主题
《K8s 节点 CPU 异常与宿主机假死深度排查实战》
前言
教材定位与使用说明
本教材面向生产环境中的 Kubernetes 节点性能故障排查与预防,是一本从 Linux 内核底层机制出发、贯穿 K8s 全栈的实战型技术教材。
适用读者:Linux 运维工程师、SRE、K8s 平台工程师、云原生架构师。
前置知识要求:
Linux 内核基础:进程管理、内存管理、文件系统、网络协议栈
K8s 核心概念:Pod、Node、CNI、Kubelet、Cgroup、Namespace
Shell 脚本基础与常用性能工具(top、vmstat、iostat)
教学方法:理论 30% + 实战 Lab 50% + 案例复盘 20%。每章遵循"故障注入 → 排查定位 → 修复优化 → 预防基线"闭环。
实验环境要求:
K8s 1.28+(推荐 Cgroup v2)
Linux Kernel 5.15+(推荐 6.1 LTS)
可观测栈:Prometheus + Grafana + Loki + Alertmanager
eBPF 工具链:BCC 0.29+、Cilium 1.15+、Parca
至少 3 节点集群(1 控制面 + 2 工作节点),每节点 ≥4C/16G
第一章:K8s 节点 CPU 异常的认知革命
1.1 学习目标
完成本章学习后,你将能够:
解释为什么 K8s 监控面板的 CPU 使用率会"骗人",理解 CFS 时间片幻觉与 Throttling 假象
区分 Load Average、CPU Usage、CFS Throttling、D-State 四种指标的本质差异
掌握"先止血→后查因→先系统→后应用→先宏观→后微观"三阶排查法
建立正确的 K8s 节点性能排查心智模型
1.2 为什么 K8s 的 CPU 监控会"骗人"?
1.2.1 CFS 调度器的"时间片"幻觉
在传统的物理机或虚拟机上,CPU 使用率通常能真实反映系统的繁忙程度。但在 K8s 中,容器被分配了 requests 和 limits,Linux CFS(Completely Fair Scheduler,完全公平调度器)会根据 Cgroup 权重分配时间片。
核心机制:CFS 以 100ms(sched_latency_ns,默认 100000000ns)为一个调度周期,将 CPU 时间按权重分配给各 Cgroup。当容器触及 cpu.cfs_quota_us 上限时,该 Cgroup 下所有线程被强制挂起,直到下一个周期。
幻觉场景:
假设一台 8 核节点上运行了一个绑核(CPU Affinity)的时延敏感型服务(绑定在 CPU 3 上),同时有一个非绑核的批处理容器。当批处理容器的线程被调度到 CPU 3 上时:
Plain
CPU 3 时间线:
|--- 延迟敏感服务线程 ---|--- 批处理线程 ---|--- 延迟敏感服务线程 ---|
↑ 排队等待 ↑ 排队等待此时:
整体 CPU 使用率:可能只有 50%(因为其他核心空闲)
CPU 3 的 Run Queue:从 1 飙升到 3-5
业务 RT(响应时间):从 2ms 飙升到 50ms+
Load Average:显著升高
监控面板告诉你"系统不忙",但业务已经在超时。
验证命令:
Bash
# 查看每个 CPU 核心的运行队列长度
cat /proc/loadavg
# 输出:12.5 8.3 5.1 3/512 28456
# 第4个字段 3/512 表示:当前运行线程数/总线程数
# 查看特定 CPU 核心的调度统计
cat /proc/schedstat
# 或使用 perf sched latency 查看特定进程的调度延迟
# 查看 Cgroup 的 CPU 统计
cat /sys/fs/cgroup/kubepods.slice/kubepods-pod<UID>.slice/cpu.stat
# 关注:nr_periods, nr_throttled, throttled_usec1.2.2 Throttling(限流)带来的"假象"
当容器触及 CPU Limits 时,CFS 会强制暂停该容器内的所有进程。cAdvisor 采集的 container_cpu_usage_seconds_total 可能显示该容器 CPU 使用率达到了 100%(即达到了 Limits 上限),但实际上:
应用内部的线程因为被频繁挂起而处于饥饿状态
业务请求在排队等待,超时率飙升
这种"高使用率"掩盖了应用正在被系统严重限流的真相
关键指标对比:
验证命令:
Bash
# 找到目标 Pod 的 Cgroup 路径
POD_UID=$(kubectl get pod <pod-name> -o jsonpath='{.metadata.uid}')
CGROUP_PATH="/sys/fs/cgroup/kubepods.slice/kubepods-pod${POD_UID}.slice"
# Cgroup v2 查看限流统计
cat ${CGROUP_PATH}/cpu.stat
# 输出示例:
# usage_usec 45230000
# user_usec 38100000
# system_usec 7130000
# nr_periods 15230
# nr_throttled 892 ← 被限流 892 个周期!
# throttled_usec 4560000 ← 累计被限流 4.56 秒
# 计算限流比例
echo "scale=2; 892/15230*100" | bc
# 输出:5.85(%)1.2.3 四种关键指标的本质区别
1.3 宿主机假死与监控断连的元凶分类
当节点出现"假死"(SSH 登录缓慢、命令无响应)且监控数据断断续续时,问题已深入内核态或硬件层。用户态的监控 Agent(如 Node Exporter)因无法获取 CPU 时间片或陷入 D 状态而停止上报。
1.3.1 元凶分类矩阵
1.3.2 D 状态进程与 I/O 阻塞
Linux 进程在等待磁盘 I/O 完成时会进入 D 状态(TASK_UNINTERRUPTIBLE),此时进程不可被 kill -9 终止。如果底层存储(云盘、NAS、CSI Volume)出现严重延迟,或文件系统元数据操作卡死,会导致大量进程堆积在 D 状态。
Bash
# 快速统计 D 状态进程数
ps -eo state | grep -c D
# 查看 D 状态进程详情及其等待的内核函数
ps -eo pid,stat,wchan:32,comm | grep " D"
# 输出示例:
# 12345 D io_schedule containerd-shim
# 12678 D nfs4_call_sync_sequence java
# 12901 D blk_mq_get_tag python3
# 查看 I/O 等待的内核栈
cat /proc/12345/stack
# 输出示例:
# [<0>] io_schedule+0x16/0x40
# [<0>] blk_mq_get_tag+0x1a2/0x280
# [<0>] blk_mq_alloc_request+0x8f/0x1e01.3.3 软中断(SoftIRQ)风暴
网络流量突增或负载均衡策略不当,可能导致某个 CPU 核心的软中断处理不过来。此时 ksoftirqd/N 进程占用 100% CPU。由于软中断优先级极高,它会完全剥夺用户态进程的执行时间。
Bash
# 查看各 CPU 核心的中断分布
mpstat -P ALL 1 3
# 关注 %soft 列,如果某核持续 >80% 即为异常
# 查看中断亲和性
cat /proc/interrupts | head -5
cat /proc/irq/<IRQ_NUM>/smp_affinity_list
# 查看软中断统计
cat /proc/softirqs
# NET_RX 和 NET_TX 是最常见的网络软中断1.3.4 内核态锁竞争与 Soft Lockup
某些内核 Bug(如特定版本的 cgroup 内存回收死锁、conntrack 表锁竞争)或硬件驱动缺陷,可能导致 CPU 陷入死循环。
Bash
# 检查内核日志中的 Soft Lockup
dmesg -T | grep -i "soft lockup"
# 输出示例:
# [Mon Jun 9 03:22:15 2025] watchdog: BUG: soft lockup - CPU#3 stuck for 23s! [kworker/3:1:12345]
# 检查 RCU stall(另一种内核卡死表现)
dmesg -T | grep -i "rcu_sched self-detected stall"1.4 运维视角的排查黄金法则
法则一:先止血,后查因
在生产环境中,业务可用性高于一切。在开始深度排查前,如果业务已经受损,应第一时间采取止血动作:
Bash
# 第一步:隔离故障节点(禁止新 Pod 调度)
kubectl cordon <node-name>
# 第二步:驱逐现有 Pod(优雅迁移)
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data --timeout=300s
# 第三步:网关层限流/降级(如果 Pod 迁移需要时间)
# 以 Nginx Ingress 为例
kubectl annotate ingress <ingress-name> \
nginx.ingress.kubernetes.io/limit-rps="100" \
nginx.ingress.kubernetes.io/limit-connections="50"止血有效性验证(三项必查):
✅ 热点核心利用率下降到 ≤75%
✅ 业务 RT 在 3-5 分钟内回落到基线
✅ TCP 重传率和超时率同步下降
法则二:先系统,后应用
严禁一上来就分析业务代码火焰图。必须先确认操作系统层面是否存在瓶颈:
Bash
# 系统态快速画像(30秒内完成)
top -bn1 | head -20 # 整体 CPU/内存/进程概览
mpstat -P ALL 1 3 # 核级 CPU 分布(%usr/%sys/%soft/%wa/%steal)
vmstat 1 5 # 上下文切换/中断/运行队列
iostat -xz 1 3 # 磁盘 I/O 饱和度
ss -s # 网络连接状态统计判断逻辑:
%sys > 30%→ 内核态瓶颈(第二章)%wa > 20%→ I/O 阻塞(第二章)%soft > 50%→ 软中断风暴(第二章)%steal > 10%→ 虚拟化争抢(第三章)以上均正常 → 再进入应用层分析(第四章)
法则三:先宏观,后微观
先通过 top、vmstat、sar 观察整体资源画像,锁定瓶颈方向后,再使用 perf、eBPF 进行精准定位。
严禁行为:
🚫 在假死节点上直接运行
perf record -F 999(高频采样会加剧负载)🚫 在 CPU 100% 的节点上运行
strace -p <PID>(ptrace 停顿可能成为压垮骆驼的最后一根稻草)🚫 在不确定方向时同时挂载多个重量级工具
1.5 实战 Lab
Lab 1.1:CFS 时间片幻觉复现
目标:观察绑核服务被非绑核线程干扰时,监控面板与实际延迟的背离。
Bash
# 步骤1:创建绑核的延迟敏感服务(模拟)
# 在节点上运行一个绑定到 CPU 3 的循环程序
taskset -c 3 sh -c 'while true; do :; done' &
SENSITIVE_PID=$!
# 步骤2:创建非绑核的批处理负载
stress-ng --cpu 4 --cpu-method matrixprod --timeout 60s &
# 步骤3:观察 CPU 3 的运行队列
watch -n 0.5 'cat /proc/schedstat | awk "NR==4{print}"'
# 第4行对应 CPU 3
# 步骤4:测量延迟敏感服务的调度延迟
perf sched record -p $SENSITIVE_PID -- sleep 10
perf sched latency -p $SENSITIVE_PID
# 步骤5:对比整体 CPU 使用率
mpstat -P ALL 1 10
# 观察:整体 Usage 可能只有 50%,但 CPU 3 的 %sys 和调度延迟极高
# 清理
kill $SENSITIVE_PIDLab 1.2:CFS Throttling 触发与观测
目标:触发容器 CPU Throttling,对比 cpu.stat 与 cAdvisor 指标差异。
YAML
# throttle-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: throttle-test
spec:
containers:
- name: stress
image: polinux/stress
command: ["stress", "--cpu", "4", "--timeout", "300"]
resources:
requests:
cpu: "500m"
limits:
cpu: "1" # 限制为1核,但stress启动4个线程
restartPolicy: NeverBash
# 部署
kubectl apply -f throttle-test.yaml
# 等待 Pod 运行后,查看 Cgroup 限流统计
POD_UID=$(kubectl get pod throttle-test -o jsonpath='{.metadata.uid}')
CONTAINER_ID=$(crictl ps --name stress -q)
CGROUP_PATH=$(crictl inspect $CONTAINER_ID | jq -r '.info.runtimeSpec.linux.cgroupsPath')
# Cgroup v2
cat /sys/fs/cgroup/${CGROUP_PATH}/cpu.stat
# 观察 nr_throttled 持续增长
# 同时在 Prometheus 中查询
# container_cpu_cfs_throttled_periods_total{pod="throttle-test"}
# 对比 cpu.stat 中的 nr_periods 和 nr_throttled1.6 最佳实践 Checklist
[ ] 禁止仅凭 CPU Usage 判断节点健康,必须联合 Load、Throttling、D-State、Steal 四维评估
[ ] 止血动作必须包含:cordon + drain + 网关限流,且验证 3 项恢复指标
[ ] 严禁在假死节点直接挂载 perf/strace 等重量级工具
[ ] 排查顺序严格遵循:系统态 → 虚拟化层 → 容器运行时 → 应用层
[ ] 每次排查必须记录时间线、使用的命令、观察到的数据,形成 RCA 素材
1.7 常见误区
1.8 思考题
如果你的节点有 64 个 CPU 核心,Load Average 为 80,但 CPU Usage 只有 40%,可能的原因有哪些?你会如何进一步排查?
一个 Java 应用设置了 CPU Limit = 2,监控显示 CPU 使用率持续在 1.8 左右,但业务 P99 延迟从 10ms 飙升到 500ms。请分析可能的原因并给出排查步骤。
为什么在 K8s 环境中,传统的"CPU 使用率 > 80% 告警"策略是不足的?你会设计怎样的多维告警规则?
第二章:系统态开销与内核瓶颈剖析
2.1 学习目标
完成本章学习后,你将能够:
使用 USE 方法(Utilization / Saturation / Errors)系统化定位系统态瓶颈
掌握软中断绑定、I/O 队列、conntrack 表溢出的完整排查路径
区分 %sys、%wa、%soft 三大系统态开销的根因并实施修复
配置 IRQ Affinity、RPS/RFS 优化网络中断处理
2.2 USE 方法论框架
在深入具体场景前,先建立系统化的排查框架。USE 方法由性能分析大师 Brendan Gregg 提出:
排查顺序:对每种资源(CPU、内存、磁盘、网络),依次检查 U → S → E。
2.3 软中断(SoftIRQ)与 ksoftirqd 飙高排查
2.3.1 机制详解
在 K8s 集群中,网络流量呈现"高并发、小包多"的特征(微服务 gRPC 调用、Service Mesh Sidecar 通信、K8s API Server 心跳)。当网卡接收到大量数据包时:
Plain
网络包到达 → 硬件中断(Hard IRQ) → 触发软中断(NET_RX) → ksoftirqd 处理
↓
NAPI poll → 协议栈解析 → socket 投递Linux 内核将耗时的数据包处理逻辑交给软中断,由 ksoftirqd/N 内核线程执行。当某个核心的软中断处理速率跟不上包到达速率时,就会形成"软中断风暴"。
2.3.2 现象与根因
2.3.3 实战排查路径
Bash
# 第一步:确认中断分布
mpstat -P ALL 1 5
# 输出示例(异常):
# CPU %usr %nice %sys %iowait %irq %soft %steal %guest %idle
# 0 2.10 0.00 3.20 0.50 0.10 92.30 0.00 0.00 1.80 ← 异常!
# 1 15.20 0.00 5.10 0.30 0.05 0.20 0.00 0.00 79.15
# 2 14.80 0.00 4.90 0.40 0.08 0.15 0.00 0.00 79.67
# 3 15.50 0.00 5.30 0.20 0.06 0.18 0.00 0.00 78.76
# 第二步:查看中断亲和性
cat /proc/interrupts | grep -i eth
# 输出示例:
# CPU0 CPU1 CPU2 CPU3
# 28: 89234567 123456 134567 145678 PCI-MSI eth0-rx-0
# 29: 123456 89345678 134567 145678 PCI-MSI eth0-rx-1
# ← 所有 rx-0 队列的中断都打到了 CPU0!
# 第三步:查看软中断统计
cat /proc/softirqs
# 关注 NET_RX 行的分布
# 第四步:查看网络包速率
sar -n DEV 1 5
# 关注 rxpck/s(每秒接收包数),如果 >100K 且集中在单核,即为小包洪峰
# 第五步:查看 softnet 队列溢出
cat /proc/net/softnet_stat
# 每行3个十六进制数:处理的包数 / 丢弃的包数 / 时间挤压次数
# 如果第二列(丢弃)非零,说明软中断处理不过来2.3.4 修复方案:IRQ Affinity 与 RPS/RFS
Bash
# 方案一:手动设置 IRQ 亲和性(将中断分散到多个核心)
# 查看网卡 IRQ 号
grep eth0 /proc/interrupts | awk '{print $1}' | tr -d ':'
# 假设 IRQ 号为 28, 29, 30, 31
# 将 4 个队列分别绑定到 4 个核心
echo 1 > /proc/irq/28/smp_affinity # CPU 0
echo 2 > /proc/irq/29/smp_affinity # CPU 1
echo 4 > /proc/irq/30/smp_affinity # CPU 2
echo 8 > /proc/irq/31/smp_affinity # CPU 3
# 方案二:使用 irqbalance 服务(自动平衡)
systemctl enable irqbalance
systemctl start irqbalance
# 方案三:配置 RPS(Receive Packet Steering)- 软件级多核分发
# 将 eth0 的 rx-0 队列的 RPS 掩码设为所有核心
echo "f" > /sys/class/net/eth0/queues/rx-0/rps_cpus
# "f" = 二进制 1111 = CPU 0-3
# 方案四:配置 RFS(Receive Flow Steering)- 按连接哈希分发
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt生产环境持久化配置(使用 udev 规则):
Bash
# /etc/udev/rules.d/99-irq-affinity.rules
ACTION=="add", KERNEL=="eth0", RUN+="/usr/local/bin/set-irq-affinity.sh"Bash
#!/bin/bash
# /usr/local/bin/set-irq-affinity.sh
# 将网卡中断均匀分布到所有核心,避开 CPU 0(预留给系统)
IRQ_LIST=$(grep eth0 /proc/interrupts | awk '{print $1}' | tr -d ':')
CPU_COUNT=$(nproc)
CPU_IDX=1 # 从 CPU 1 开始,避开 CPU 0
for irq in $IRQ_LIST; do
echo $CPU_IDX > /proc/irq/$irq/smp_affinity_list
CPU_IDX=$(( (CPU_IDX + 1) % CPU_COUNT ))
[ $CPU_IDX -eq 0 ] && CPU_IDX=1
done2.4 磁盘 I/O 等待(%wa)与内核阻塞
2.4.1 机制详解
CPU 异常有时只是表象,真正的瓶颈在于存储 I/O。当进程发起文件读写请求时,如果底层存储响应缓慢,CPU 进入 I/O 等待状态(%wa)。虽然 CPU 并没有在"计算",但大量进程排队等待 I/O 会导致 Load 飙升。
K8s 节点常见的 I/O 风暴源:
containerd-shim:容器日志写入(尤其是未配置日志轮转时)kubelet:镜像拉取、PLEG 状态同步CSI 插件:云盘挂载/卸载操作
应用容器:大量小文件写入(如 Java 应用的 GC 日志)
2.4.2 实战排查路径
Bash
# 第一步:确认 I/O 瓶颈
iostat -xz 1 5
# 输出示例(异常):
# Device r/s w/s rkB/s wkB/s await avgqu-sz %util
# nvme0n1 1250 3800 50000 152000 45.2 228.5 99.8 ← 磁盘饱和!
#
# 关键指标:
# %util > 90%:磁盘已饱和
# await > 20ms(SSD)或 > 50ms(HDD):I/O 延迟异常
# avgqu-sz > 32:队列严重堆积
# 第二步:定位 I/O 热点进程
iotop -oPa
# -o:只显示有 I/O 的进程
# -P:显示进程而非线程
# -a:累积模式
# 输出示例:
# PID PRIO USER DISK READ DISK WRITE TOTAL COMMAND
# 12345 be/4 root 0.00 B/s 850.00 M/s 850M containerd-shim -namespace k8s.io
# 12678 be/4 root 0.00 B/s 120.00 M/s 120M java -jar app.jar
# 第三步:追踪 D 状态进程
ps -eo pid,stat,wchan:32,comm | awk '$2 ~ /D/'
# 输出示例:
# 12345 D io_schedule containerd-shim
# 12901 D nfs4_call_sync_sequence java
# 第四步:查看特定进程的 I/O 统计
cat /proc/12345/io
# 输出:
# rchar: 1234567890
# wchar: 9876543210
# read_bytes: 123456789
# write_bytes: 987654321
# cancelled_write_bytes: 0
# 第五步:使用 pidstat 追踪进程级 I/O
pidstat -d 1 5
# 显示每个进程的 kB_rd/s 和 kB_wr/s2.4.3 K8s 特有的 I/O 问题
问题一:容器日志风暴
Bash
# 查看容器日志大小
du -sh /var/log/pods/*/*/*.log | sort -rh | head -10
# 查看 kubelet 日志轮转配置
cat /var/lib/kubelet/config.yaml | grep -A5 containerLog
# 推荐配置:
# containerLogMaxSize: "100Mi"
# containerLogMaxFiles: 5问题二:镜像拉取 I/O 风暴
Bash
# 查看 containerd 的并发下载配置
cat /etc/containerd/config.toml | grep -A5 "plugins.\"io.containerd.grpc.v1.cri\""
# 推荐:max_concurrent_downloads = 3(默认)
# 查看当前镜像拉取进程
ps aux | grep "containerd.*pull"问题三:CSI 云盘 IOPS 耗尽
Bash
# 查看云盘 IOPS 使用情况(以阿里云为例)
# 通过云监控 API 或 CLI 查询
aliyun ecs DescribeDiskMonitorData --DiskId d-xxx --Period 60
# 在节点上验证
iostat -xz 1 | grep vd # 云盘设备通常为 vda/vdb2.5 网络协议栈瓶颈与连接风暴
2.5.1 conntrack 表溢出
K8s 节点上的每个 Service 连接都会被 conntrack 表追踪。当短连接并发量突破阈值时:
Bash
# 查看 conntrack 表使用情况
conntrack -C
# 输出:262144(当前表项数)
sysctl net.netfilter.nf_conntrack_max
# 输出:net.netfilter.nf_conntrack_max = 262144
# 计算使用率
echo "scale=2; 262144/262144*100" | bc
# 100%!表已满!
# 查看 conntrack 统计(含丢弃计数)
conntrack -S
# 输出示例:
# cpu=0 found=0 invalid=12345 insert=0 insert_failed=0 drop=89012 ← drop 非零!
# early_drop=89012 ← 因表满而丢弃的连接数
# 查看内核日志中的 conntrack 溢出告警
dmesg -T | grep "nf_conntrack: table full"
# [Mon Jun 9 10:22:15 2025] nf_conntrack: nf_conntrack: table full, dropping packet修复方案:
Bash
# 临时扩容
sysctl -w net.netfilter.nf_conntrack_max=1048576
sysctl -w net.netfilter.nf_conntrack_buckets=262144
# 持久化
cat >> /etc/sysctl.d/99-k8s-network.conf << EOF
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_buckets = 262144
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 30
EOF
sysctl --system2.5.2 iptables/IPVS 规则膨胀
在大规模集群中(>1000 Service),iptables 规则匹配成为性能瓶颈:
Bash
# 查看 iptables 规则数量
iptables -L -n -t nat | wc -l
# 如果 > 10000 行,每次数据包匹配都需要线性遍历
# 查看 IPVS 规则(如果使用 IPVS 模式)
ipvsadm -Ln | wc -l
# 使用 perf 验证协议栈热点
perf top -g -e cycles -- sleep 10
# 如果看到 ipt_do_table、nf_conntrack_in、nf_hook_slow 占据 >20% CPU
# 说明网络协议栈已成为瓶颈优化路径:
2.5.3 连接状态异常
Bash
# 使用 ss 查看连接状态分布(替代 netstat,性能更好)
ss -s
# 输出示例:
# Total: 45230
# TCP: 38920 (estab 12345, closed 18900, orphaned 234, timewait 15678)
# 查看 TIME_WAIT 堆积
ss -tan state time-wait | wc -l
# 如果 > 10000,考虑启用 tcp_tw_reuse
# 查看 CLOSE_WAIT 堆积(通常意味着应用未正确关闭连接)
ss -tan state close-wait | wc -l
# 如果持续增长,说明应用存在连接泄漏
# 查看 SYN 队列溢出
netstat -s | grep -i "syn"
# 关注 "SYNs to LISTEN sockets dropped" 和 "times the listen queue of a socket overflowed"2.6 实战 Lab
Lab 2.1:软中断风暴模拟与修复
Bash
# 步骤1:使用 hping3 制造小包洪峰(在另一台机器上执行)
hping3 -S -p 80 --flood <target-node-ip>
# 步骤2:在目标节点观察
mpstat -P ALL 1
# 观察某核 %soft 飙升
# 步骤3:查看中断分布
watch -n 1 'cat /proc/interrupts | grep eth0'
# 步骤4:配置 RPS 分散负载
echo "f" > /sys/class/net/eth0/queues/rx-0/rps_cpus
# 步骤5:验证修复效果
mpstat -P ALL 1
# %soft 应分散到多个核心,单核不再 100%Lab 2.2:conntrack 表溢出复现
Bash
# 步骤1:缩小 conntrack 表(模拟溢出)
sysctl -w net.netfilter.nf_conntrack_max=1024
# 步骤2:制造大量短连接
for i in $(seq 1 2000); do
curl -s -o /dev/null http://<service-ip>:80 &
done
# 步骤3:观察溢出
dmesg -T | tail -5
# 应看到 "nf_conntrack: table full, dropping packet"
conntrack -S | grep drop
# drop 计数应 > 0
# 步骤4:恢复并扩容
sysctl -w net.netfilter.nf_conntrack_max=10485762.7 生产 SOP:系统态快速巡检
Bash
#!/bin/bash
# system-health-check.sh - 系统态快速巡检(30秒内完成)
echo "=== CPU 核级分布 ==="
mpstat -P ALL 1 3
echo "=== 磁盘 I/O ==="
iostat -xz 1 3
echo "=== 网络连接状态 ==="
ss -s
echo "=== Conntrack 使用率 ==="
CURRENT=$(conntrack -C 2>/dev/null || echo 0)
MAX=$(sysctl -n net.netfilter.nf_conntrack_max)
echo "Current: $CURRENT / Max: $MAX ($(echo "scale=1; $CURRENT/$MAX*100" | bc)%)"
echo "=== D-State 进程 ==="
ps -eo pid,stat,wchan:32,comm | awk '$2 ~ /D/' | head -10
echo "=== 软中断溢出 ==="
cat /proc/net/softnet_stat | awk '{print "CPU"NR": processed="$1" dropped="$2" time_squeeze="$3}'
echo "=== 内核错误日志(最近5分钟)==="
dmesg -T | tail -20 | grep -iE "error|fail|oom|lockup|panic"2.8 最佳实践 Checklist
[ ] 所有 K8s 节点必须配置 IRQ Affinity,避免中断集中在单核
[ ] 启用 RPS/RFS,将网络包处理分散到多核
[ ] conntrack 表大小设置为预期最大连接数的 2 倍,并配置告警(使用率 > 80%)
[ ] 容器日志必须配置轮转:
containerLogMaxSize: 100Mi,containerLogMaxFiles: 5[ ] 大规模集群(>1000 Service)必须使用 IPVS 或 eBPF 模式替代 iptables
[ ] 禁止使用
netstat -an排查大连接数问题(会卡死节点),使用ss -s
2.9 常见误区
2.10 思考题
一个 K8s 节点运行了 200 个 Pod,每个 Pod 每秒发起 100 个短连接。请计算 conntrack 表的压力,并设计合理的
nf_conntrack_max值。你发现节点的 %sys 持续在 40% 以上,但
perf top显示的热点函数是copy_user_generic_unrolled。这意味着什么?你会如何进一步优化?为什么在 K8s 节点上,
containerd-shim进程经常成为 I/O 风暴的源头?有哪些预防措施?
第三章:虚拟化与 Cgroup 资源争抢深度机制
3.1 学习目标
完成本章学习后,你将能够:
量化 %steal 对业务的影响并制定云实例选型策略
深入理解 Cgroup v1/v2 CFS 带宽控制机制,解决 Throttling 引发的 GC/探针超时
配置 Topology Manager 实现 NUMA 资源亲和
掌握 CPU Burst 技术的原理与部署方法
3.2 CPU Steal 时间:被 Hypervisor 偷走的算力
3.2.1 机制详解
在物理机上,CPU 时间完全属于你;但在虚拟机上,CPU 时间是你与"邻居"抢来的。%steal 代表了虚拟机(Guest OS)准备运行,但 Hypervisor 因资源被其他虚拟机占用而无法分配物理 CPU 时间片的百分比。
Plain
物理 CPU 时间线:
|--- VM-A(你的节点)---|--- VM-B(邻居)---|--- VM-A ---|--- VM-C ---|
↑ 你的进程在等待 ↑ 恢复执行
这段时间 = Steal Time3.2.2 影响量化
3.2.3 排查与应对
Bash
# 确认 Steal 时间
mpstat -P ALL 1 10
# 关注 %steal 列
# 使用 vmstat 观察整体
vmstat 1 10
# 第16列(st)即为 steal time
# 查看 Hypervisor 类型
systemd-detect-virt
# 输出:kvm / xen / vmware / none
# 查看 CPU 型号与虚拟化特性
lscpu | grep -i "hypervisor\|virtual"应对策略:
3.3 CFS 调度器与 CPU Throttling 深度机制
3.3.1 CFS 带宽控制原理
Linux CFS 通过两个参数控制 Cgroup 的 CPU 配额:
cpu.cfs_period_us(Cgroup v1)/cpu.max** 的第二个值**(Cgroup v2):调度周期,默认 100000μs(100ms)cpu.cfs_quota_us(Cgroup v1)/cpu.max** 的第一个值**(Cgroup v2):每个周期内允许使用的 CPU 时间
Plain
示例:CPU Limit = 1 核
cpu.cfs_period_us = 100000 (100ms)
cpu.cfs_quota_us = 100000 (100ms)
→ 每 100ms 内,该 Cgroup 最多使用 100ms CPU 时间
示例:CPU Limit = 0.5 核
cpu.cfs_quota_us = 50000 (50ms)
→ 每 100ms 内,该 Cgroup 最多使用 50ms CPU 时间关键问题:当配额用完时,Cgroup 下所有线程被强制挂起,包括:
业务处理线程
GC 线程(Java/Go)
健康检查响应线程
日志写入线程
3.3.2 Java 应用的 GC 灾难
这是 K8s 环境中最具迷惑性的问题之一:
Plain
时间线:
|--- 业务线程运行 ---|--- GC 触发 ---|--- CFS 配额用完 ---|
↓
所有线程被挂起(包括 GC 线程)
↓
GC STW 时间从 50ms 延长到 500ms+
↓
K8s Liveness Probe 超时(默认 1s)
↓
Pod 被 Kill 并重启
↓
重启后再次触发 GC → 循环验证方法:
Bash
# 查看 Java 应用的 GC 日志
kubectl logs <pod-name> | grep -i "pause"
# 输出示例:
# [GC pause (G1 Evacuation Pause) (young), 0.5234567 secs] ← 正常应 < 0.1s
# 查看 Cgroup 限流统计
cat /sys/fs/cgroup/kubepods.slice/.../cpu.stat
# nr_throttled: 1523 ← 被限流 1523 次
# throttled_usec: 8900000 ← 累计被限流 8.9 秒
# 关联 Pod 重启事件
kubectl describe pod <pod-name> | grep -A5 "Events"
# 输出:
# Warning Unhealthy 2m kubelet Liveness probe failed: HTTP probe failed with statuscode: 503
# Normal Killing 2m kubelet Container failed liveness probe, will be restarted3.3.3 修复方案
方案一:调整 CPU Limit
YAML
# 对于延迟敏感型应用,建议:
resources:
requests:
cpu: "2" # 保证调度资源
limits:
cpu: "4" # 允许突发到 2 倍
# 或者干脆不设 CPU Limit(仅设 Requests)方案二:启用 CPU Burst(Linux 5.14+)
CPU Burst 允许容器在空闲时积累"CPU 信用",在突发时使用,避免硬性截断。
Bash
# 检查内核是否支持
cat /sys/fs/cgroup/cpu.max.burst # 如果文件存在则支持
# Kubelet 配置启用(K8s 1.28+)
# kubelet-config.yaml
featureGates:
CPUBurst: trueYAML
# Pod 级别配置
apiVersion: v1
kind: Pod
metadata:
annotations:
cpu-burst.kubernetes.io/burst: "100000" # 允许额外突发 100ms
spec:
containers:
- name: app
resources:
limits:
cpu: "1"方案三:调整 CFS 周期(谨慎使用)
Bash
# 将 CFS 周期从 100ms 缩短到 10ms(减少单次挂起时间)
# 注意:会增加调度开销
echo 10000 > /sys/fs/cgroup/kubepods.slice/cpu.cfs_period_us3.3.4 Cgroup v1 vs v2 对比
Bash
# 查看当前 Cgroup 版本
stat -fc %T /sys/fs/cgroup/
# cgroup2fs = Cgroup v2
# tmpfs = Cgroup v1
# Cgroup v2 查看 CPU 限制
cat /sys/fs/cgroup/kubepods.slice/cpu.max
# 输出:100000 100000 (quota=100ms, period=100ms → 1核)
# Cgroup v2 查看压力指标(PSI)
cat /sys/fs/cgroup/kubepods.slice/cpu.pressure
# 输出:some avg10=5.23 avg60=3.12 avg300=2.45 total=123456789
# avg10=5.23 表示最近10秒内,有5.23%的时间至少一个任务因CPU不足而等待3.4 NUMA 架构与内存带宽争抢
3.4.1 机制详解
在高性能计算或数据库类 K8s 节点上,物理 CPU 采用 NUMA(Non-Uniform Memory Access)架构:
Plain
┌─────────────────────────────────────────┐
│ 物理服务器 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Socket 0 │ │ Socket 1 │ │
│ │ CPU 0-15 │ │ CPU 16-31 │ │
│ │ 本地内存 │←→│ 本地内存 │ │
│ │ (NUMA 0) │QPI│ (NUMA 1) │ │
│ └──────────────┘ └──────────────┘ │
│ ↑ 本地访问:~80ns │
│ ↑ 远程访问:~140ns(+75%延迟) │
└─────────────────────────────────────────┘3.4.2 排查方法
Bash
# 查看 NUMA 拓扑
numactl -H
# 输出示例:
# available: 2 nodes (0-1)
# node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# node 0 size: 64000 MB
# node 1 cpus: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
# node 1 size: 64000 MB
# node distances:
# node 0 1
# 0: 10 21 ← 本地访问距离10,远程访问距离21(+110%)
# 1: 21 10
# 查看 NUMA 内存访问统计
numastat -m
# 关注 numa_hit(本地命中)和 numa_miss(远程访问)
# 如果 numa_miss / (numa_hit + numa_miss) > 20%,说明跨 NUMA 访问严重
# 查看特定进程的 NUMA 内存分布
numastat -p <PID>3.4.3 K8s NUMA 亲和配置
YAML
# KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cpuManagerPolicy: "static" # 启用 CPU 绑核
topologyManagerPolicy: "single-numa-node" # 强制单 NUMA 分配
topologyManagerScope: "pod" # Pod 级别对齐
reservedSystemCPUs: "0,1" # 预留给系统的 CPU
memoryManagerPolicy: "Static" # 内存也按 NUMA 分配YAML
# Pod 配置(必须 Requests = Limits 才能触发 CPU Manager)
apiVersion: v1
kind: Pod
metadata:
name: numa-sensitive-app
spec:
containers:
- name: redis
image: redis:7-alpine
resources:
requests:
cpu: "4"
memory: "8Gi"
limits:
cpu: "4" # 必须等于 requests
memory: "8Gi" # 必须等于 requests3.5 实战 Lab
Lab 3.1:CFS Throttling 导致 Java GC 超时
Bash
# 步骤1:部署 Java 应用(设置 1 核 Limit)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: java-throttle-test
spec:
containers:
- name: java
image: openjdk:17-slim
command: ["java", "-Xmx512m", "-XX:+UseG1GC", "-jar", "/app/app.jar"]
resources:
requests:
cpu: "500m"
limits:
cpu: "1"
EOF
# 步骤2:注入突发流量
kubectl exec -it java-throttle-test -- ab -n 10000 -c 100 http://localhost:8080/api
# 步骤3:观察 Throttling
POD_UID=$(kubectl get pod java-throttle-test -o jsonpath='{.metadata.uid}')
watch -n 1 "cat /sys/fs/cgroup/kubepods.slice/kubepods-pod${POD_UID}.slice/cpu.stat"
# 观察 nr_throttled 快速增长
# 步骤4:观察 GC 日志
kubectl logs java-throttle-test | grep "pause"
# GC pause 时间应从正常的 20-50ms 飙升到 200-500ms
# 步骤5:移除 Limit 后对比
kubectl patch pod java-throttle-test --type json \
-p='[{"op":"remove","path":"/spec/containers/0/resources/limits/cpu"}]'
# 注意:需要重建 Pod 才能生效Lab 3.2:NUMA 跨节点访问性能对比
Bash
# 步骤1:查看 NUMA 拓扑
numactl -H
# 步骤2:本地 NUMA 运行 Redis
numactl --cpunodebind=0 --membind=0 redis-server --port 6379 &
# 步骤3:跨 NUMA 运行 Redis
numactl --cpunodebind=0 --membind=1 redis-server --port 6380 &
# 步骤4:性能对比
redis-benchmark -p 6379 -t set,get -n 100000 -c 50 # 本地
redis-benchmark -p 6380 -t set,get -n 100000 -c 50 # 跨 NUMA
# 对比 P99 延迟差异(通常 20-40%)3.6 最佳实践 Checklist
[ ] 延迟敏感型服务使用独占型实例或专有宿主机,消除 Steal
[ ] Java/Go 应用的 CPU Limit 至少为 Requests 的 2 倍,或不设 Limit
[ ] 启用 CPU Burst(内核 5.14+),减少突发流量下的 Throttling
[ ] 高性能应用(Redis/ES/DB)必须配置 NUMA 亲和(Topology Manager + CPU Manager)
[ ] Java 应用 Memory Limit ≥ Heap × 1.5,预留 Metaspace/线程栈/Native 内存
[ ] 使用 PSI(Pressure Stall Information)替代传统指标评估资源压力
3.7 常见误区
3.8 思考题
一个 Java 应用设置了 CPU Limit = 2,JVM 参数
-XX:ActiveProcessorCount=4。这会导致什么问题?如何修复?在 Cgroup v2 环境下,
cpu.pressure显示some avg10=15.0。这意味着什么?你会采取什么行动?为什么 K8s 的 CPU Manager
static策略要求 Requests = Limits?如果不相等会发生什么?
第四章:eBPF 与 Perf 微观性能透视
4.1 学习目标
完成本章学习后,你将能够:
理解 eBPF 相比 strace 的零侵入优势与安全边界
使用 BCC/Cilium Hubble 定位 TCP 重传、DNS 延迟、HTTP/gRPC 瓶颈
生成并解读 On-CPU/Off-CPU 火焰图
构建 eBPF + cAdvisor + 业务 RED 指标的交叉验证矩阵
4.2 告别 strace:传统排查手段的致命缺陷
4.2.1 strace 的三大问题
4.2.2 eBPF 的革命性优势
eBPF(Extended Berkeley Packet Filter)允许在不修改内核源码、不加载内核模块的前提下,动态向内核注入安全的"探针"代码:
Plain
传统方式:
用户态进程 → ptrace 暂停 → 读取寄存器 → 恢复执行(高开销)
eBPF 方式:
内核事件触发 → eBPF 虚拟机执行探针代码 → 结果写入 Map → 用户态读取(零停顿)关键优势:
零侵入:不暂停目标进程,不影响业务执行
内核可见:可以 hook 任何内核函数、tracepoint、kprobe
安全:eBPF Verifier 确保程序不会崩溃内核
高效:JIT 编译为本地机器码,开销 < 1%
4.3 eBPF 实战:网络问题透视
4.3.1 TCP 重传定位
当应用出现偶发超时,但节点带宽未跑满时,很可能是 TCP 重传:
Bash
# 使用 BCC tcptracer 监控 TCP 事件
/usr/share/bcc/tools/tcptracer
# 只关注重传事件
/usr/share/bcc/tools/tcptracer -R
# 输出示例:
# Tracing TCP events...
# T PID COMM IP SADDR DADDR SPORT DPORT
# R 12345 java 4 10.0.1.5 10.0.2.10 45678 8080
# R 12345 java 4 10.0.1.5 10.0.2.10 45679 8080
# ← 大量重传指向同一目标 10.0.2.10:8080
# 使用 tcpdrop 查看丢包原因
/usr/share/bcc/tools/tcpdrop
# 输出示例:
# TIME PID IP SADDR:DPORT DADDR:SPORT STATE (TCPFLAGS)
# 10:22:15 12345 4 10.0.1.5:45678 10.0.2.10:8080 ESTABLISHED (ACK)
# tcp_drop+0x1
# tcp_rcv_state_process+0x1a2
# tcp_v4_do_rcv+0x8f
# ← 内核栈显示丢包发生在接收处理阶段4.3.2 DNS 解析延迟分析
K8s 环境中,CoreDNS 延迟直接影响服务发现:
Bash
# 使用 gethostlatency 监控 DNS 解析耗时
/usr/share/bcc/tools/gethostlatency
# 输出示例:
# TIME PID COMM LATms HOST
# 10:22:15 12345 java 0.05 service-a.default.svc.cluster.local
# 10:22:16 12345 java 152.30 service-b.default.svc.cluster.local ← 异常!
# 10:22:17 12678 python3 0.03 service-a.default.svc.cluster.local
# 如果延迟 > 100ms,进一步排查:
# 1. CoreDNS Pod 状态
kubectl get pods -n kube-system -l k8s-app=kube-dns -o wide
# 2. CoreDNS 延迟指标(Prometheus)
# coredns_dns_request_duration_seconds_bucket
# 3. 节点 conntrack UDP 冲突(常见根因!)
conntrack -S | grep "insert_failed"
# 如果 insert_failed > 0,说明 UDP conntrack 冲突导致 DNS 包丢失DNS 延迟的常见根因:
4.3.3 Cilium Hubble:全链路网络可视化
Bash
# 安装 Hubble CLI
hubble observe --pod java-app --type drop
# 输出示例:
# 🚀 Flow: java-app-7d8f9 -> service-b:8080 DROPPED (Policy denied)
# 查看特定 Pod 的 TCP 重传
hubble observe --pod java-app --protocol tcp --verdict DROPPED
# 查看 DNS 查询延迟
hubble observe --type l7 --protocol dns --pod java-app4.4 Perf 实战:火焰图定位热点函数
4.4.1 安全采样原则
4.4.2 On-CPU 火焰图(CPU 消耗在哪里)
Bash
# 步骤1:找到目标容器的主进程 PID
CONTAINER_ID=$(crictl ps --name java-app -q)
PID=$(crictl inspect $CONTAINER_ID | jq '.info.pid')
# 步骤2:安全采样(99Hz,30秒)
perf record -F 99 -p $PID -g -- sleep 30
# 步骤3:生成火焰图
perf script | \
/opt/FlameGraph/stackcollapse-perf.pl | \
/opt/FlameGraph/flamegraph.pl \
--title "On-CPU Flame Graph - java-app" \
--colors java \
> oncpu-flamegraph.svg
# 步骤4:解读火焰图
# - 宽度 = CPU 时间占比(越宽越耗时)
# - 高度 = 调用栈深度
# - 平顶山 = 热点函数(优化目标)
# - 红色 = 用户态代码
# - 黄色/橙色 = 内核态代码4.4.3 Off-CPU 火焰图(CPU 等待在哪里)
Off-CPU 分析比 On-CPU 更重要——它揭示进程为什么慢(在等什么):
Bash
# 使用 BCC offcputime 工具
/usr/share/bcc/tools/offcputime -df -p $PID 30 > offcpu.stacks
# 生成 Off-CPU 火焰图
/opt/FlameGraph/flamegraph.pl \
--title "Off-CPU Flame Graph" \
--colors io \
--countname "us" \
offcpu.stacks > offcpu-flamegraph.svg
# 解读:
# - 如果大量时间在 io_schedule → 等待磁盘 I/O
# - 如果大量时间在 futex_wait → 用户态锁竞争
# - 如果大量时间在 mutex_lock → 内核态锁竞争
# - 如果大量时间在 tcp_recvmsg → 等待网络数据
# - 如果大量时间在 schedule → 被 CFS Throttling 挂起4.4.4 使用 eBPF 替代 Perf(更安全)
Bash
# 使用 BCC profile 工具(基于 eBPF,比 perf 更安全)
/usr/share/bcc/tools/profile -df -p $PID 30 > profile.stacks
# 使用 cpudist 查看调度延迟分布
/usr/share/bcc/tools/cpudist -p $PID 10
# 输出示例:
# usecs : count distribution
# 0 -> 1 : 0 | |
# 2 -> 3 : 125 |********************|
# 4 -> 7 : 89 |************** |
# 8 -> 15 : 45 |******* |
# 16 -> 31 : 12 |* |
# 32 -> 63 : 3 | |
# 1024 -> 2047 : 8 |* | ← 异常!被 Throttling
# 2048 -> 4095 : 5 | |
# 使用 runqlat 查看运行队列延迟
/usr/share/bcc/tools/runqlat -p $PID 104.5 交叉验证矩阵
eBPF/Perf 提供微观视角,必须与宏观指标交叉验证:
4.6 实战 Lab
Lab 4.1:CoreDNS 延迟注入与 eBPF 定位
Bash
# 步骤1:在 CoreDNS Pod 上注入延迟(使用 tc netem)
COREDNS_POD=$(kubectl get pods -n kube-system -l k8s-app=kube-dns -o name | head -1)
kubectl exec -n kube-system $COREDNS_POD -- tc qdisc add dev eth0 root netem delay 100ms
# 步骤2:在业务节点使用 gethostlatency 观察
/usr/share/bcc/tools/gethostlatency
# 应看到所有 DNS 查询延迟 > 100ms
# 步骤3:使用 tcptracer 观察 UDP 重传
/usr/share/bcc/tools/tcptracer # 注意:UDP 需要用 udptracer 或 Hubble
# 步骤4:检查 conntrack UDP 冲突
conntrack -S | grep insert_failed
# 步骤5:清理
kubectl exec -n kube-system $COREDNS_POD -- tc qdisc del dev eth0 rootLab 4.2:Off-CPU 火焰图定位锁竞争
Bash
# 步骤1:部署一个有锁竞争的应用
# (使用一个多线程 Java 应用,大量 synchronized 块)
# 步骤2:生成 Off-CPU 火焰图
PID=$(crictl inspect $(crictl ps --name java-app -q) | jq '.info.pid')
/usr/share/bcc/tools/offcputime -df -p $PID 30 > offcpu.stacks
/opt/FlameGraph/flamegraph.pl --colors io offcpu.stacks > offcpu.svg
# 步骤3:分析火焰图
# 如果看到大量时间在:
# futex_wait_queue_me → futex_wait → do_futex → __x64_sys_futex
# 说明用户态锁竞争严重
# 步骤4:使用 perf 验证
perf record -F 99 -p $PID -g -- sleep 10
perf report
# 确认热点函数4.7 工具链速查表
4.8 安全红线
🚫 生产节点禁止使用
perf record -F 999,采样频率 ≤ 99Hz🚫 eBPF 程序必须经过 Verifier,禁止在 < 4.15 内核加载未签名探针
🚫 禁止在 CPU > 90% 或 Load > 2×核心数 的节点上启动新的 eBPF 程序
✅ 优先使用 DaemonSet 部署只读 eBPF Agent,避免 SSH 登录假死节点
✅ 所有 eBPF 工具必须设置超时(
--duration),防止无限运行
4.9 最佳实践 Checklist
[ ] 每个 K8s 节点部署 BCC 工具集(DaemonSet 或按需安装)
[ ] 大规模集群部署 Cilium + Hubble 替代 iptables + tcpdump
[ ] 部署 Parca/Pyroscope 持续剖析平台,保留 7 天历史火焰图
[ ] 建立 eBPF → cAdvisor → RED 三层交叉验证 SOP
[ ] 所有 eBPF 排查操作记录在 RCA 报告中,包含工具版本和参数
4.10 思考题
为什么
strace -p <PID>在高并发 Java 应用上可能导致服务完全不可用?eBPF 如何避免这个问题?Off-CPU 火焰图显示大量时间在
schedule()函数。这一定意味着 CPU 不足吗?还有哪些可能?在不重启 Pod、不修改代码的前提下,如何定位一个 Go 应用的 goroutine 锁竞争问题?
第五章:节点假死与崩溃现场取证
5.1 学习目标
完成本章学习后,你将能够:
配置 Serial Console 与 kdump,确保假死/panic 时仍可获取现场
使用 NodeProblemDetector + 自定义 kmsg 规则捕获内核级异常
构建"黑匣子"自动快照机制,解决"故障后无证据"难题
掌握假死节点的五步取证法
5.2 为什么需要"黑匣子"机制
当节点彻底假死(SSH 连不上、Kubelet 无响应、监控断连)时,传统排查手段全部失效:
核心问题:故障发生后,现场证据消失。我们需要一套独立于用户态的"黑匣子"机制。
5.3 Serial Console(串行控制台)
5.3.1 原理
Serial Console 通过硬件串口(UART)直接连接 CPU,绕过所有软件层(包括内核调度器)。即使系统完全假死,只要 CPU 还在执行指令,Serial Console 就能输出内核日志。
5.3.2 配置方法
云平台配置(以阿里云/AWS 为例):
Bash
# 阿里云 ECS:在控制台启用"串行控制台"
# AWS EC2:启用 EC2 Serial Console
# 配置 GRUB 输出到串口
# /etc/default/grub
GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8"
GRUB_TERMINAL="serial console"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"
# 更新 GRUB
grub2-mkconfig -o /boot/grub2/grub.cfg本地访问:
Bash
# 物理机:通过 IPMI/BMC 的 SOL(Serial Over LAN)
ipmitool -H <BMC_IP> -U admin -P password sol activate
# 云平台:通过 Web 控制台或 CLI
# 阿里云
aliyun ecs DescribeInstanceConsoleOutput --InstanceId i-xxx
# AWS
aws ec2 get-console-output --instance-id i-xxx5.3.3 假死时的关键信息
通过 Serial Console,你可以看到:
Plain
[ 123.456789] NMI watchdog: BUG: soft lockup - CPU#3 stuck for 23s! [kworker/3:1:12345]
[ 123.456790] Modules linked in: nf_conntrack iptable_nat ...
[ 123.456791] CPU: 3 PID: 12345 Comm: kworker/3:1 Not tainted 5.15.0-91-generic
[ 123.456792] RIP: 0010:native_queued_spin_lock_slowpath+0x1c2/0x2e0
[ 123.456793] Call Trace:
[ 123.456794] _raw_spin_lock+0x28/0x30
[ 123.456795] nf_conntrack_confirm+0x25/0x50
[ 123.456796] nf_hook_slow+0x43/0xc0关键信息提取:
soft lockup:CPU 软锁死,内核态死循环RIP:出错时的指令地址(定位到具体函数)Call Trace:内核调用栈(定位到具体代码路径)Modules linked in:加载的内核模块(排查驱动问题)
5.4 kdump:内核崩溃转储
5.4.1 原理
kdump 在内核 panic 时,启动一个预留的迷你内核(capture kernel),将崩溃时的完整内存镜像(vmcore)写入磁盘。事后可以使用 crash 工具分析 vmcore。
Plain
正常运行内核(Production Kernel)
↓ panic 触发
kexec 跳转
↓
预留的捕获内核(Capture Kernel)
↓
将崩溃时的内存 dump 到 /var/crash/vmcore
↓
重启回正常内核5.4.2 配置方法
Bash
# 步骤1:预留崩溃内核内存
# /etc/default/grub
GRUB_CMDLINE_LINUX="crashkernel=512M"
grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
# 步骤2:安装 kdump 工具
# RHEL/CentOS
yum install kexec-tools crash
systemctl enable kdump
systemctl start kdump
# Ubuntu/Debian
apt install kdump-tools crash
systemctl enable kdump-tools
# 步骤3:验证 kdump 就绪
kdumpctl status
# 输出:Kdump is operational
# 步骤4:配置 vmcore 存储路径
# /etc/kdump.conf
path /var/crash
core_collector makedumpfile -l --message-level 7 -d 31
# -d 31:压缩并过滤无用页面,减小 vmcore 体积
# 步骤5:测试 kdump(谨慎!会导致节点重启)
echo 1 > /proc/sys/kernel/sysrq
echo c > /proc/sysrq-trigger
# 节点会 panic → kdump → 重启
# 重启后检查 /var/crash/ 下是否有 vmcore5.4.3 分析 vmcore
Bash
# 使用 crash 工具打开 vmcore
crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/127.0.0.1-2025-06-09-10:22:15/vmcore
# 在 crash 交互界面中:
crash> bt # 查看 panic 时的调用栈
crash> bt -a # 查看所有 CPU 的调用栈
crash> ps # 查看进程列表
crash> ps -m # 查看 D-State 进程
crash> log # 查看内核日志(dmesg)
crash> dev -d # 查看磁盘设备
crash> net # 查看网络设备
crash> kmem -i # 查看内存使用
crash> mod # 查看加载的内核模块
crash> dis -l <address> # 反汇编特定地址5.5 NodeProblemDetector(NPD)深度配置
5.5.1 工作机制
NPD 是 K8s 生态中用于监控节点健康状况的守护进程,将底层系统问题转化为 K8s API Server 能理解的 NodeCondition 或 Event:
Plain
系统日志/脚本 → NPD 检测 → NodeCondition/Event → K8s Scheduler → 调度决策
↓
Taint 效应
(阻止新 Pod 调度)5.5.2 生产级配置
JSON
{
"plugin": "kmsg",
"pluginConfig": {
"source": "kmsg",
"logPath": "/dev/kmsg",
"lookback": "5m",
"bufferSize": 1024,
"source": "kmsg",
"metricsReporting": true
},
"logPath": "/dev/kmsg",
"lookback": "5m",
"bufferSize": 1024,
"source": "kmsg",
"conditions": [
{
"type": "KernelSoftLockup",
"reason": "NoSoftLockup",
"message": "Node has no soft lockup"
},
{
"type": "ReadonlyFilesystem",
"reason": "FilesystemIsNotReadOnly",
"message": "Filesystem is not read-only"
},
{
"type": "OOMKilling",
"reason": "NoOOMKilling",
"message": "No OOM killing detected"
}
],
"rules": [
{
"type": "permanent",
"condition": "KernelSoftLockup",
"reason": "KernelHasSoftLockup",
"pattern": "NMI watchdog: BUG: Soft Lockup",
"message": "CPU soft lockup detected"
},
{
"type": "permanent",
"condition": "ReadonlyFilesystem",
"reason": "FilesystemIsReadOnly",
"pattern": "Remounting filesystem read-only",
"message": "Filesystem has been remounted read-only"
},
{
"type": "temporary",
"reason": "OOMKilling",
"pattern": "Killed process \\d+ (.+) total-vm.*",
"message": "OOM killing detected"
},
{
"type": "permanent",
"condition": "KernelDeadlock",
"reason": "KernelHasDeadlock",
"pattern": "task \\S+ blocked for more than \\d+ seconds",
"message": "Kernel task blocked (possible deadlock)"
},
{
"type": "permanent",
"condition": "RCUStall",
"reason": "KernelHasRCUStall",
"pattern": "rcu_sched self-detected stall on CPU",
"message": "RCU stall detected"
}
]
}5.5.3 部署为 Static Pod(关键!)
NPD 必须以 Static Pod 或 systemd service 运行,不能依赖 Kubelet 健康:
YAML
# /etc/kubernetes/manifests/node-problem-detector.yaml(Static Pod)
apiVersion: v1
kind: Pod
metadata:
name: node-problem-detector
namespace: kube-system
labels:
app: node-problem-detector
spec:
priorityClassName: system-node-critical
hostNetwork: true
hostPID: true
containers:
- name: node-problem-detector
image: registry.k8s.io/node-problem-detector/node-problem-detector:v0.8.18
command:
- /node-problem-detector
- --logtostderr
- --config.system-log-monitor=/config/kernel-monitor.json
- --config.custom-plugin-monitor=/config/custom-monitor.json
securityContext:
privileged: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
volumeMounts:
- name: kmsg
mountPath: /dev/kmsg
readOnly: true
- name: config
mountPath: /config
readOnly: true
- name: localtime
mountPath: /etc/localtime
readOnly: true
volumes:
- name: kmsg
hostPath:
path: /dev/kmsg
- name: config
hostPath:
path: /etc/npd/config
- name: localtime
hostPath:
path: /etc/localtime5.6 Sysstat 黑匣子:长期采样
5.6.1 配置
Bash
# 安装 sysstat
yum install sysstat # RHEL
apt install sysstat # Debian
# 配置 10 秒采样间隔
# /etc/sysconfig/sysstat(RHEL)或 /etc/default/sysstat(Debian)
HISTORY=31 # 保留 31 天
COMPRESSAFTER=7 # 7 天后压缩
# /etc/cron.d/sysstat
# 修改采样间隔为 10 秒
*/1 * * * * root /usr/lib64/sa/sa1 6 10
# 含义:每分钟执行一次,每次采样 6 个数据点,间隔 10 秒
# 启用服务
systemctl enable sysstat
systemctl start sysstat5.6.2 故障后回溯
Bash
# 查看特定日期的 CPU 数据
sar -P ALL -f /var/log/sa/sa09
# sa09 = 6月9日的数据
# 查看负载
sar -q -f /var/log/sa/sa09
# 查看网络
sar -n DEV -f /var/log/sa/sa09
# 查看磁盘 I/O
sar -d -f /var/log/sa/sa09
# 查看内存
sar -r -f /var/log/sa/sa09
# 查看特定时间段(10:20-10:30)
sar -P ALL -f /var/log/sa/sa09 -s 10:20:00 -e 10:30:005.7 SOP:假死节点取证五步法
Plain
┌─────────────────────────────────────────────────────────┐
│ 假死节点取证五步法 │
├─────────────────────────────────────────────────────────┤
│ │
│ 步骤1:尝试带外接入 │
│ ├─ Serial Console / IPMI SOL / 云平台控制台 │
│ └─ 如果可接入 → 步骤2 │
│ └─ 如果不可接入 → 步骤3 │
│ │
│ 步骤2:在线取证(节点仍可响应串口) │
│ ├─ dmesg -T > /tmp/kmsg.txt │
│ ├─ sar -A > /tmp/sar.txt │
│ ├─ ps auxf > /tmp/ps.txt │
│ ├─ cat /proc/loadavg > /tmp/load.txt │
│ └─ cat /proc/softirqs > /tmp/softirqs.txt │
│ │
│ 步骤3:离线取证(节点完全无响应) │
│ ├─ 检查 /var/crash/ 下是否有 vmcore(kdump) │
│ ├─ 检查云平台控制台截图/日志 │
│ └─ 检查 NPD 上报的最后一个 Event │
│ │
│ 步骤4:关联分析 │
│ ├─ 提取 NPD Event 时间戳 │
│ ├─ 提取 Prometheus 断点前最后数据 │
│ ├─ 提取 sysstat 对应时间段数据 │
│ └─ 关联告警 ID、变更窗口、部署记录 │
│ │
│ 步骤5:归档与 RCA │
│ ├─ 所有证据上传至 S3/Loki │
│ ├─ 关联告警 ID 与时间窗 │
│ └─ 撰写 RCA 报告(见第十章模板) │
│ │
└─────────────────────────────────────────────────────────┘5.8 实战 Lab
Lab 5.1:触发 Soft Lockup 并通过 Serial Console 捕获
Bash
# 步骤1:确认 Serial Console 已配置
dmesg | grep "console.*ttyS0"
# 步骤2:使用内核模块触发 soft lockup(仅测试环境!)
# 加载一个会死循环的内核模块
cat > /tmp/lockup_test.c << 'EOF'
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
static int __init lockup_init(void) {
printk(KERN_ERR "Triggering soft lockup for testing...\n");
while(1) { mdelay(1000); } // 死循环
return 0;
}
static void __exit lockup_exit(void) {}
module_init(lockup_init);
module_exit(lockup_exit);
MODULE_LICENSE("GPL");
EOF
# 编译并加载(会导致一个 CPU 核心锁死!)
make -C /lib/modules/$(uname -r)/build M=/tmp modules
insmod /tmp/lockup_test.ko
# 步骤3:等待 NMI watchdog 触发(默认 20 秒)
# 通过 Serial Console 观察输出:
# "NMI watchdog: BUG: soft lockup - CPU#X stuck for XXs!"
# 步骤4:验证 NPD 是否捕获
kubectl get events --field-selector reason=KernelHasSoftLockupLab 5.2:kdump 配置与 vmcore 分析
Bash
# 步骤1:配置 kdump(见 5.4.2)
kdumpctl status # 确认 operational
# 步骤2:触发 panic(仅测试环境!)
echo 1 > /proc/sys/kernel/sysrq
echo c > /proc/sysrq-trigger
# 步骤3:节点重启后,检查 vmcore
ls -lh /var/crash/
# 输出:
# drwxr-xr-x. 2 root root 4096 Jun 9 10:25 127.0.0.1-2025-06-09-10:22:15
# 步骤4:分析 vmcore
crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux \
/var/crash/127.0.0.1-2025-06-09-10:22:15/vmcore
crash> bt
# 查看 panic 调用栈
crash> log | tail -50
# 查看 panic 前的内核日志5.9 最佳实践 Checklist
[ ] 所有生产节点必须配置 Serial Console(云平台)或 IPMI SOL(物理机)
[ ] 所有生产节点必须启用 kdump,并验证 vmcore 生成路径有足够空间
[ ] NPD 必须以 Static Pod 部署,不依赖 Kubelet 健康
[ ] sysstat 采样间隔 ≤ 10 秒,数据保留 ≥ 31 天
[ ] 假死节点取证必须在重启前完成(或确认 kdump 已生成 vmcore)
[ ] 所有取证数据自动归档至对象存储,关联告警 ID
5.10 思考题
为什么 NPD 不能以普通 DaemonSet 部署?在什么场景下 DaemonSet 会失效?
kdump 预留的
crashkernel=512M内存对节点有什么影响?在大内存节点上应该如何调整?如果节点假死且没有配置 Serial Console 和 kdump,你还有哪些方法获取故障信息?
第六章:自动化诊断与自愈体系
6.1 学习目标
完成本章学习后,你将能够:
设计 NPD + 自定义脚本 + K8s 驱逐的三层自愈闭环
配置 Kubelet 驱逐阈值与 QoS 优先级保障
构建 Ansible/Argo Workflows 驱动的故障响应流水线
实现从"人工救火"到"自动免疫"的运维模式转变
6.2 三层自愈架构
Plain
┌─────────────────────────────────────────────────────────────┐
│ 三层自愈架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 第一层:感知(Detection) │
│ ├─ NPD:内核级异常(soft lockup / OOM / readonly fs) │
│ ├─ 自定义脚本:PLEG 超时 / containerd D-State / 网络连通性 │
│ ├─ Prometheus 告警:资源阈值 / 业务 RED 指标 │
│ └─ Kubelet 内置:节点条件(MemoryPressure / DiskPressure) │
│ │
│ 第二层:隔离(Isolation) │
│ ├─ 自动打污点(Taint):阻止新 Pod 调度 │
│ ├─ 网关限流/降级:减少故障节点流量 │
│ └─ 服务网格熔断:Istio/Linkerd 自动熔断 │
│ │
│ 第三层:恢复(Recovery) │
│ ├─ Kubelet 驱逐:按 QoS 优先级逐步驱逐 Pod │
│ ├─ kubectl drain:优雅迁移现有 Pod │
│ ├─ ASG/CA 替换:终止故障实例,创建新节点 │
│ └─ 物理机带外重启:IPMI/BMC 远程重启 │
│ │
└─────────────────────────────────────────────────────────────┘6.3 自定义诊断脚本实战
6.3.1 PLEG 健康检查
PLEG(Pod Lifecycle Event Generator)负责维护容器运行时状态与 K8s API 的一致性。当 containerd 响应缓慢时,PLEG 超时,节点变为 NotReady。
Bash
#!/bin/bash
# /opt/npd/plugins/check_pleg.sh
# NPD 自定义插件:检测 PLEG 健康状态
KUBELET_LOG="/var/log/kubelet.log"
THRESHOLD=5
TIME_WINDOW="5m"
# 获取最近5分钟内 PLEG 报错次数
ERROR_COUNT=$(journalctl -u kubelet --since "${TIME_WINDOW} ago" 2>/dev/null | \
grep -c "PLEG is not healthy" || echo 0)
if [ "$ERROR_COUNT" -ge "$THRESHOLD" ]; then
echo "PLEG unhealthy: ${ERROR_COUNT} errors in last ${TIME_WINDOW}"
exit 1 # 非零退出码触发 NPD 告警
fi
echo "PLEG healthy"
exit 06.3.2 综合节点健康检查
Bash
#!/bin/bash
# /opt/npd/plugins/node_health_check.sh
# 综合节点健康检查脚本
ISSUES=()
# 检查1:containerd 是否处于 D 状态
CONTAINERD_STATE=$(ps -eo stat,comm | grep containerd | awk '{print $1}')
if ; then
ISSUES+=("containerd in D-state")
fi
# 检查2:磁盘 I/O 饱和度
DISK_UTIL=$(iostat -xz 1 2 | tail -n +4 | awk '{if($NF+0 > 95) print $1}')
if [ -n "$DISK_UTIL" ]; then
ISSUES+=("Disk saturated: $DISK_UTIL")
fi
# 检查3:APIServer 连通性
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
if ! curl -sk --connect-timeout 5 "${APISERVER}/healthz" | grep -q "ok"; then
ISSUES+=("APIServer unreachable")
fi
# 检查4:TIME_WAIT 连接数
TW_COUNT=$(ss -tan state time-wait | wc -l)
if [ "$TW_COUNT" -gt 20000 ]; then
ISSUES+=("TIME_WAIT exhaustion: $TW_COUNT")
fi
# 检查5:D-State 进程数
D_COUNT=$(ps -eo state | grep -c D)
if [ "$D_COUNT" -gt 10 ]; then
ISSUES+=("D-state processes: $D_COUNT")
fi
# 输出结果
if [ ${#ISSUES[@]} -gt 0 ]; then
echo "Node unhealthy: ${ISSUES[*]}"
exit 1
fi
echo "Node healthy"
exit 06.3.3 NPD 自定义插件配置
JSON
{
"plugin": "custom",
"pluginConfig": {
"invoke_interval": "30s",
"timeout": "10s",
"max_output_length": 256,
"concurrency": 3
},
"source": "node-health-check",
"conditions": [
{
"type": "NodeHealthy",
"reason": "NodeIsHealthy",
"message": "Node is healthy"
}
],
"rules": [
{
"type": "permanent",
"condition": "NodeHealthy",
"reason": "NodeIsUnhealthy",
"path": "/opt/npd/plugins/node_health_check.sh",
"message": "Node health check failed"
}
]
}6.4 Kubelet 驱逐策略精细配置
6.4.1 驱逐阈值
YAML
# KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
# 硬驱逐:立即触发,不等待优雅退出
evictionHard:
memory.available: "100Mi"
nodefs.available: "5%"
nodefs.inodesFree: "5%"
imagefs.available: "10%"
pid.available: "100"
# 软驱逐:持续一段时间后才触发
evictionSoft:
memory.available: "250Mi"
nodefs.available: "10%"
imagefs.available: "15%"
evictionSoftGracePeriod:
memory.available: "30s"
nodefs.available: "30s"
imagefs.available: "30s"
# 驱逐后的最小回收量
evictionMinimumReclaim:
memory.available: "500Mi"
nodefs.available: "1Gi"
imagefs.available: "2Gi"
# 驱逐压力下的 Pod 终止宽限期
evictionMaxPodGracePeriod: 30
# 资源预留
kubeReserved:
cpu: "500m"
memory: "1Gi"
ephemeral-storage: "10Gi"
systemReserved:
cpu: "250m"
memory: "512Mi"
ephemeral-storage: "5Gi"6.4.2 QoS 驱逐顺序
K8s 在资源压力下按以下顺序驱逐 Pod:
Plain
驱逐顺序(从先到后):
1. BestEffort(无 Requests/Limits)
2. Burstable(Requests < Limits)
3. Guaranteed(Requests = Limits)← 最后被驱逐
同 QoS 等级内,按以下优先级:
- 实际使用量超过 Requests 最多的优先驱逐
- Priority 值低的优先驱逐6.4.3 PodDisruptionBudget 保护
YAML
# 确保核心服务在驱逐时不会全部下线
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: core-api-pdb
spec:
minAvailable: "80%" # 至少 80% 的 Pod 保持可用
selector:
matchLabels:
app: core-api
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: database-pdb
spec:
maxUnavailable: 1 # 最多 1 个 Pod 不可用
selector:
matchLabels:
app: postgresql6.5 自动化运维流水线
6.5.1 Alertmanager Webhook → Ansible 自愈
YAML
# Alertmanager 配置
route:
receiver: 'auto-remediation'
routes:
- match:
alertname: NodeCPULoadHigh
severity: critical
receiver: 'auto-remediation'
continue: true
receivers:
- name: 'auto-remediation'
webhook_configs:
- url: 'http://ansible-tower:8080/api/v2/job_templates/42/launch/'
http_config:
bearer_token: '<token>'
send_resolved: trueYAML
# Ansible Playbook: auto-remediate-node.yml
---
- name: Auto Remediate Unhealthy Node
hosts: localhost
vars:
node_name: "{{ alertmanager_payload.commonLabels.instance }}"
tasks:
- name: Cordon the node
command: kubectl cordon {{ node_name }}
- name: Wait for node to be unschedulable
command: kubectl get node {{ node_name }} -o jsonpath='{.spec.unschedulable}'
register: cordon_status
until: cordon_status.stdout == "true"
retries: 5
delay: 5
- name: Drain the node
command: >
kubectl drain {{ node_name }}
--ignore-daemonsets
--delete-emptydir-data
--timeout=300s
--force
register: drain_result
failed_when: false
- name: Collect diagnostics before reboot
command: >
ssh {{ node_name }}
"dmesg -T > /tmp/diag_dmesg.txt;
sar -A > /tmp/diag_sar.txt;
ps auxf > /tmp/diag_ps.txt;
tar czf /tmp/diag_$(date +%s).tar.gz /tmp/diag_*.txt"
failed_when: false
- name: Upload diagnostics to S3
command: >
aws s3 cp /tmp/diag_*.tar.gz
s3://ops-diagnostics/{{ node_name }}/$(date +%Y%m%d)/
failed_when: false
- name: Trigger ASG instance replacement (cloud)
command: >
aws autoscaling terminate-instance-in-auto-scaling-group
--instance-id {{ instance_id }}
--should-decrement-desired-capacity
when: cloud_provider == "aws"6.5.2 自愈闭环验证
Bash
# 验证自愈流水线完整性
# 1. 模拟节点 CPU 过载
stress-ng --cpu $(nproc) --timeout 300s &
# 2. 等待告警触发(Prometheus → Alertmanager → Webhook)
# 观察 Alertmanager UI:http://alertmanager:9093
# 3. 验证节点被 cordon
kubectl get node <node-name> -o jsonpath='{.spec.unschedulable}'
# 应输出:true
# 4. 验证 Pod 被驱逐
kubectl get pods --field-selector spec.nodeName=<node-name>
# 应只剩 DaemonSet Pod
# 5. 验证诊断数据已上传
aws s3 ls s3://ops-diagnostics/<node-name>/
# 6. 验证新节点加入(ASG 场景)
kubectl get nodes -w6.6 最佳实践 Checklist
[ ] NPD 必须以 Static Pod 或 systemd service 运行,不依赖 Kubelet 健康
[ ] 诊断脚本执行超时 ≤ 10s,禁止调用
docker ps等可能阻塞的命令[ ] 自动驱逐前必须检查 PodDisruptionBudget,防止核心服务全部下线
[ ] 自愈动作必须记录审计日志:触发条件、执行命令、结果状态、操作人(系统)
[ ] 每月进行一次自愈流水线演练(Game Day),验证端到端有效性
[ ] 驱逐超时设置合理值(300s),避免 PDB 阻塞导致 drain 永远无法完成
6.7 思考题
如果 NPD 检测到节点异常并打了 Taint,但 Kubelet 本身已经假死无法执行驱逐,你会如何设计兜底方案?
在混合云环境(部分物理机 + 部分云 VM)中,如何统一自愈流水线?物理机的"替换"操作应该如何实现?
自动驱逐可能触发"驱逐风暴"(大量 Pod 同时迁移导致其他节点过载)。你会如何设计防护机制?
第七章:可观测性盲区治理与监控重构
7.1 学习目标
完成本章学习后,你将能够:
分析监控断连的三大根因(采集超时/网络拥塞/OOM 误伤)
构建双路采集 + Remote Write + Static Pod 兜底的高可用架构
实施告警分级、收敛、抑制与自动化现场快照
设计故障回溯能力,消灭"无法复现"的借口
7.2 监控数据断连的根因分析
7.2.1 三大根因
7.2.2 断连时间线分析
Plain
正常状态:
Prometheus ──scrape──→ Node Exporter ──→ 指标数据 ──→ TSDB
每15s 200 OK
故障开始(CPU 100%):
Prometheus ──scrape──→ Node Exporter ──→ 超时(无 CPU 时间片)
每15s timeout
故障加剧(网络拥塞):
Prometheus ──scrape──→ [网络丢包] ──→ 完全断连
每15s DROP
故障恢复后:
Prometheus ──scrape──→ Node Exporter ──→ 200 OK(但中间数据永久丢失)7.3 高可用采集架构
7.3.1 架构设计
Plain
┌─────────────────────────────────────────────────────────────┐
│ 高可用监控采集架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 节点层: │
│ ├─ Node Exporter(Static Pod, system-node-critical) │
│ ├─ Prometheus Agent(DaemonSet, remote_write 模式) │
│ └─ eBPF Agent(DaemonSet, 持续剖析) │
│ │
│ 传输层: │
│ ├─ remote_write → VictoriaMetrics(主) │
│ └─ remote_write → Thanos Receive(备) │
│ │
│ 存储层: │
│ ├─ VictoriaMetrics(热数据 7天) │
│ ├─ Thanos Store(温数据 30天,S3 后端) │
│ └─ S3/GCS(冷数据 1年) │
│ │
│ 展示层: │
│ ├─ Grafana(统一查询) │
│ └─ Alertmanager(告警路由) │
│ │
└─────────────────────────────────────────────────────────────┘7.3.2 Node Exporter Static Pod 配置
YAML
# /etc/kubernetes/manifests/node-exporter.yaml
apiVersion: v1
kind: Pod
metadata:
name: node-exporter
namespace: monitoring
labels:
app: node-exporter
spec:
priorityClassName: system-node-critical # 最高优先级,最后被驱逐
hostNetwork: true
hostPID: true
containers:
- name: node-exporter
image: prom/node-exporter:v1.7.0
args:
- --path.procfs=/host/proc
- --path.sysfs=/host/sys
- --path.rootfs=/host/root
- --collector.processes
- --collector.systemd
- --web.listen-address=0.0.0.0:9100
ports:
- containerPort: 9100
hostPort: 9100
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
securityContext:
runAsUser: 0
readOnlyRootFilesystem: true
volumeMounts:
- name: proc
mountPath: /host/proc
readOnly: true
- name: sys
mountPath: /host/sys
readOnly: true
- name: root
mountPath: /host/root
readOnly: true
mountPropagation: HostToContainer
volumes:
- name: proc
hostPath:
path: /proc
- name: sys
hostPath:
path: /sys
- name: root
hostPath:
path: /
tolerations:
- operator: Exists # 容忍所有污点,确保任何节点都能运行7.3.3 Prometheus Agent 模式
YAML
# prometheus-agent.yaml
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: agent
namespace: monitoring
spec:
mode: Agent # Agent 模式:无本地 TSDB,仅 remote_write
replicas: 1
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
remoteWrite:
- url: "http://victoriametrics:8428/api/v1/write"
queueConfig:
capacity: 10000
maxShards: 50
minShards: 5
maxSamplesPerSend: 2000
batchSendDeadline: "5s"
- url: "http://thanos-receive:19291/api/v1/receive"
queueConfig:
capacity: 5000
maxShards: 20
scrapeInterval: 15s
priorityClassName: system-node-critical7.4 告警治理
7.4.1 告警分级体系
7.4.2 Alertmanager 路由与抑制
YAML
# alertmanager.yml
route:
receiver: 'default'
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
# P0:电话轰炸
- match:
severity: critical
receiver: 'pagerduty-critical'
group_wait: 10s
repeat_interval: 30m
continue: true
# P1:IM 强提醒
- match:
severity: warning
receiver: 'slack-warning'
group_wait: 30s
# P2:工单
- match:
severity: info
receiver: 'ticket-system'
# 抑制规则:节点 NotReady 时,抑制该节点所有 Pod 告警
inhibit_rules:
- source_match:
alertname: NodeNotReady
target_match_re:
alertname: 'PodCrashLooping|PodNotReady|ContainerOOMKilled'
equal: ['node']
- source_match:
alertname: NodeCPULoadHigh
target_match_re:
alertname: 'PodLatencyHigh|PodErrorRateHigh'
equal: ['node']
# 接收器配置
receivers:
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key: '<pagerduty-key>'
severity: critical
- name: 'slack-warning'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#ops-alerts'
title: '{{ .CommonAnnotations.summary }}'
text: '{{ .CommonAnnotations.description }}\nRunbook: {{ .CommonAnnotations.runbook_url }}'
- name: 'auto-remediation'
webhook_configs:
- url: 'http://ansible-tower:8080/webhook/'7.4.3 告警规则最佳实践
YAML
# 告警规则必须包含 runbook_url
groups:
- name: node-alerts
rules:
- alert: NodeCPULoadHigh
expr: |
(
node_load5{job="node-exporter"}
/ count without(cpu, mode) (node_cpu_seconds_total{mode="idle"})
) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "Node {{ $labels.instance }} load is high"
description: "5m load average is {{ $value | humanize }}x CPU count"
runbook_url: "https://wiki.internal/runbooks/node-high-load"
dashboard_url: "https://grafana.internal/d/node-overview?var-instance={{ $labels.instance }}"
- alert: NodeCPUSoftIRQStorm
expr: |
rate(node_softirqs_total{softirq="NET_RX"}[5m]) > 100000
and
rate(node_cpu_seconds_total{mode="softirq"}[5m]) > 0.8
for: 2m
labels:
severity: critical
annotations:
summary: "SoftIRQ storm on {{ $labels.instance }}"
runbook_url: "https://wiki.internal/runbooks/softirq-storm"7.5 故障现场自动快照
7.5.1 Webhook 触发诊断脚本
Bash
#!/bin/bash
# /opt/scripts/k8s-diagnose-snapshot.sh
# 由 Alertmanager Webhook 触发的自动诊断脚本
NODE_NAME=$1
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
OUTPUT_DIR="/tmp/diag_${NODE_NAME}_${TIMESTAMP}"
S3_BUCKET="s3://ops-diagnostics"
mkdir -p $OUTPUT_DIR
# 系统概览
top -bn1 > $OUTPUT_DIR/top.txt
ps -eo pid,ppid,user,stat,wchan:32,%mem,%cpu,cmd --sort=-%cpu | head -50 > $OUTPUT_DIR/ps.txt
vmstat 1 5 > $OUTPUT_DIR/vmstat.txt
mpstat -P ALL 1 3 > $OUTPUT_DIR/mpstat.txt
# 内核日志
dmesg -T > $OUTPUT_DIR/dmesg.txt
journalctl -k --since "10 minutes ago" > $OUTPUT_DIR/kernel_journal.txt
# 网络
ss -s > $OUTPUT_DIR/ss_summary.txt
ss -tanp > $OUTPUT_DIR/ss_tcp.txt
conntrack -C > $OUTPUT_DIR/conntrack_count.txt 2>/dev/null
conntrack -S > $OUTPUT_DIR/conntrack_stats.txt 2>/dev/null
netstat -s > $OUTPUT_DIR/netstat_stats.txt
# 磁盘 I/O
iostat -xz 1 3 > $OUTPUT_DIR/iostat.txt
df -h > $OUTPUT_DIR/df.txt
mount > $OUTPUT_DIR/mount.txt
# K8s 相关
crictl ps -a > $OUTPUT_DIR/containers.txt 2>/dev/null
crictl stats > $OUTPUT_DIR/container_stats.txt 2>/dev/null
journalctl -u kubelet --since "10 minutes ago" > $OUTPUT_DIR/kubelet_log.txt
# Cgroup 信息
find /sys/fs/cgroup/kubepods.slice -name "cpu.stat" -exec sh -c \
'echo "=== {} ===" && cat {}' \; > $OUTPUT_DIR/cgroup_cpu_stats.txt 2>/dev/null
# 打包上传
tar czf ${OUTPUT_DIR}.tar.gz -C /tmp $(basename $OUTPUT_DIR)
aws s3 cp ${OUTPUT_DIR}.tar.gz ${S3_BUCKET}/${NODE_NAME}/${TIMESTAMP}/
# 清理本地
rm -rf $OUTPUT_DIR ${OUTPUT_DIR}.tar.gz
echo "Diagnostics uploaded to ${S3_BUCKET}/${NODE_NAME}/${TIMESTAMP}/"7.5.2 eBPF 持续剖析(Parca/Pyroscope)
YAML
# Parca Agent DaemonSet(简化版)
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: parca-agent
namespace: monitoring
spec:
selector:
matchLabels:
app: parca-agent
template:
metadata:
labels:
app: parca-agent
spec:
priorityClassName: system-node-critical
hostPID: true
containers:
- name: parca-agent
image: ghcr.io/parca-dev/parca-agent:v0.30.0
args:
- --remote-store-address=parca-server:7070
- --sampling-rate=19 # 19Hz,极低开销
- --debug-infod-upstream-servers=debuginfod.elfutils.org
securityContext:
privileged: true
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
volumeMounts:
- name: debugfs
mountPath: /sys/kernel/debug
volumes:
- name: debugfs
hostPath:
path: /sys/kernel/debug价值:当故障发生时,运维人员可以直接在 Parca UI 中回溯到具体时间点,查看当时究竟是哪个函数占用了 CPU,彻底消灭"无法复现"的借口。
7.6 最佳实践 Checklist
[ ] Prometheus Server 禁止部署在待监控节点上,必须独立集群
[ ] 监控组件 QoS 必须为 Guaranteed,设置
priorityClassName: system-node-critical[ ] 告警消息必须包含
runbook_url,否则 P0 告警等于噪音[ ] 配置 Alertmanager 抑制规则,避免告警风暴
[ ] sysstat 10s 采样 + Alertmanager Webhook 自动快照
[ ] 部署 Parca/Pyroscope 持续剖析,保留 ≥ 7 天历史
7.7 思考题
如果 Prometheus Agent 本身因为节点 OOM 被杀死,你还有什么手段获取节点最后的状态?
告警抑制规则配置不当可能导致"静默故障"(真正的问题被抑制了)。你会如何设计防护机制?
在 Serverless K8s(如 Fargate)环境中,无法部署 DaemonSet 和 Static Pod。你会如何构建可观测性?
第八章:镜像治理与资源配额最佳实践
8.1 学习目标
完成本章学习后,你将能够:
推行 Distroless/Alpine 极简镜像,消除安全与启动性能风险
使用 Sidecar / Admission Controller / eBPF 实现无侵入可观测性
掌握 Requests/Limits 黄金比例与三大 QoS 反模式
设计镜像安全基线与准入控制策略
8.2 为什么不要在业务镜像中内置重量级工具
8.2.1 反模式的四大风险
8.2.2 Distroless 镜像实践
Dockerfile
# 多阶段构建:构建阶段使用完整工具链,运行阶段使用 Distroless
# === 构建阶段 ===
FROM golang:1.22-bookworm AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .
# === 运行阶段(Distroless)===
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/server /server
# 没有 shell、没有包管理器、没有调试工具
# 攻击面最小化
USER nonroot:nonroot
ENTRYPOINT ["/server"]镜像大小对比:
8.2.3 镜像安全准入控制
YAML
# OPA Gatekeeper / Kyverno 策略:禁止包含特定工具的镜像
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: restrict-image-tools
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
repos:
- "gcr.io/distroless/"
- "registry.internal/prod/"
---
# Kyverno 策略:禁止 latest 标签
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: Enforce
rules:
- name: require-image-tag
match:
resources:
kinds:
- Pod
validate:
message: "An image tag is required."
pattern:
spec:
containers:
- image: "*:*"
- name: validate-image-tag
match:
resources:
kinds:
- Pod
validate:
message: "Using 'latest' tag is not allowed."
pattern:
spec:
containers:
- image: "!*:latest"8.3 正确的可观测性预埋
8.3.1 Sidecar 模式
YAML
# 在 Pod 中注入调试 Sidecar(共享 Network Namespace)
apiVersion: v1
kind: Pod
metadata:
name: app-with-debug-sidecar
spec:
containers:
# 主业务容器(Distroless,无调试工具)
- name: app
image: registry.internal/prod/myapp:v1.2.3
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
# 调试 Sidecar(按需启用,平时可设 replicas=0)
- name: debug
image: nicolaka/netshoot:latest
command: ["sleep", "infinity"]
securityContext:
capabilities:
add: ["NET_ADMIN", "NET_RAW", "SYS_PTRACE"]
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 128MiBash
# 需要抓包时,进入 Sidecar(与主容器共享网络栈)
kubectl exec -it app-with-debug-sidecar -c debug -- tcpdump -i eth0 -w /tmp/capture.pcap
# 需要 DNS 诊断时
kubectl exec -it app-with-debug-sidecar -c debug -- dig service-a.default.svc.cluster.local
# 需要查看连接状态时
kubectl exec -it app-with-debug-sidecar -c debug -- ss -tanp8.3.2 OpenTelemetry Operator 自动注入
YAML
# 安装 OTel Operator 后,通过 annotation 自动注入 SDK
apiVersion: v1
kind: Pod
metadata:
name: java-app
annotations:
instrumentation.opentelemetry.io/inject-java: "true" # 自动注入 Java Agent
spec:
containers:
- name: app
image: registry.internal/prod/java-app:v2.0
# 无需修改代码,OTel Operator 自动注入 javaagent8.3.3 eBPF 节点级无侵入采集
YAML
# Grafana Beyla / Cilium Hubble:节点级 eBPF 采集
# 无需修改应用代码,无需 Sidecar,直接在节点层面采集 HTTP/gRPC 指标
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: beyla
namespace: monitoring
spec:
template:
spec:
hostPID: true
containers:
- name: beyla
image: grafana/beyla:1.5
securityContext:
privileged: true
env:
- name: BEYLA_OPEN_PORT
value: "8080,8443,9090" # 自动发现监听这些端口的进程
- name: BEYLA_PROMETHEUS_PORT
value: "9090"8.4 Requests/Limits 黄金比例
8.4.1 本质区别
Plain
Requests(请求):
├─ 调度依据:kube-scheduler 根据它决定 Pod 放在哪个节点
├─ QoS 基准:决定 Pod 的 QoS 等级
└─ 资源预留:节点为该 Pod 预留的资源量
Limits(限制):
├─ 内核硬限制:对应 cgroups 的 cpu.max / memory.max
├─ CPU 超限:CFS Throttling(挂起,不杀)
└─ 内存超限:OOM Kill(直接杀死)8.4.2 三大 QoS 配置模板
YAML
# === Guaranteed(核心服务:数据库、网关、关键微服务)===
resources:
requests:
cpu: "4"
memory: "8Gi"
limits:
cpu: "4" # 必须等于 requests
memory: "8Gi" # 必须等于 requests
# 特点:最高存活优先级,最后被驱逐
# 适用:延迟敏感、不可中断的核心服务
---
# === Burstable(普通业务服务)===
resources:
requests:
cpu: "1" # Requests = Limits 的 50%-70%
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
# 特点:允许突发使用空闲资源,资源紧张时被限制
# 适用:后端业务逻辑、非核心 Web 服务
---
# === BestEffort(批处理/离线任务)===
resources: {} # 不设置任何 Requests/Limits
# 特点:利用碎片资源,最先被驱逐
# 适用:日志清洗、CI/CD 构建、离线计算8.4.3 Java 应用特殊配置
YAML
# Java 应用资源配置
resources:
requests:
cpu: "2"
memory: "3Gi" # Heap(2G) × 1.5 = 3G
limits:
cpu: "4" # 允许 GC 突发
memory: "3Gi" # 必须 ≥ Heap × 1.5
---
# JVM 参数(与资源配置匹配)
env:
- name: JAVA_OPTS
value: >-
-Xms2g -Xmx2g
-XX:MaxRAMPercentage=75.0
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:ActiveProcessorCount=4 # 必须与 CPU Limit 匹配!
-XX:+UseContainerSupport关键公式:
Plain
Memory Limit ≥ Xmx × 1.5
= Heap + Metaspace(~256MB) + 线程栈(线程数×1MB) + Native + 安全余量
示例:Xmx=2G → Limit ≥ 3G8.4.4 反模式警示
8.5 实战 Lab
Lab 8.1:镜像瘦身对比
Bash
# 构建臃肿镜像
cat > Dockerfile.fat << 'EOF'
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
openjdk-17-jdk vim curl tcpdump net-tools strace htop
COPY app.jar /app/app.jar
CMD ["java", "-jar", "/app/app.jar"]
EOF
docker build -f Dockerfile.fat -t myapp:fat .
docker images myapp:fat
# SIZE: ~850MB
# 构建 Distroless 镜像
cat > Dockerfile.slim << 'EOF'
FROM eclipse-temurin:17-jre-alpine AS builder
COPY app.jar /app/app.jar
FROM gcr.io/distroless/java17-debian12:nonroot
COPY --from=builder /app/app.jar /app/app.jar
USER nonroot
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
EOF
docker build -f Dockerfile.slim -t myapp:slim .
docker images myapp:slim
# SIZE: ~220MB
# 对比 Pod 启动时间
time kubectl run fat-test --image=myapp:fat --restart=Never
time kubectl run slim-test --image=myapp:slim --restart=NeverLab 8.2:Burstable Pod 在节点高负载下的降级
Bash
# 步骤1:部署 Burstable Pod(Requests=0.5, Limits=2)
# 步骤2:在节点上施加 CPU 压力
stress-ng --cpu $(nproc) --timeout 120s &
# 步骤3:观察 Burstable Pod 的 CPU 使用
kubectl top pod burstable-test
# CPU 使用应被限制在 Requests 附近(0.5 核)
# 步骤4:查看 CFS 统计
cat /sys/fs/cgroup/kubepods.slice/.../cpu.stat
# nr_throttled 应 > 0
# 步骤5:压力解除后,Pod 应恢复到 Limits 附近8.6 最佳实践 Checklist
[ ] 生产镜像禁止包含 shell、包管理器、网络诊断工具
[ ] 使用多阶段构建,运行阶段基于 Distroless 或 Alpine
[ ] 所有镜像必须使用固定版本标签,禁止
latest[ ] 部署 OPA/Kyverno 准入控制,强制镜像仓库白名单
[ ] Java 应用 Memory Limit ≥ Heap × 1.5
[ ] 延迟敏感型服务优先不设 CPU Limit,仅通过 Requests 预留
[ ] 所有 Pod 必须显式声明 Requests,禁止"只设 Limits 不设 Requests"
[ ] 基于 Prometheus 历史数据(P95)设置 Requests,而非拍脑袋
8.7 思考题
如果一个 Go 应用使用 CGO 调用了 C 库,能否使用
distroless/static镜像?应该选择哪个 Distroless 变体?在 Spot/抢占式实例上运行的 Pod,QoS 等级对存活率有什么影响?你会如何设计?
如何自动化地检测集群中所有"只设 Limits 不设 Requests"的 Pod?
第九章:生产内核基线与 K8s 配置规范
9.1 学习目标
完成本章学习后,你将能够:
制定并验证 Linux 内核 sysctl 基线(网络/内存/文件系统)
配置 Kubelet 关键参数的生产级模板
选型并调优 CNI 与容器运行时(containerd),掌握 iptables → IPVS → eBPF 的演进路径
建立基线审计与合规检查机制,实现配置即代码(Configuration as Code)的 GitOps 管理
9.2 Linux 内核 sysctl 基线(补齐节标题与引导段)
引导说明
Linux 内核参数是 K8s 节点性能的"地基"。错误的 sysctl 配置可能导致:
conntrack 表溢出 → 网络丢包(第二章)
inotify 上限不足 → kubelet 无法监控容器变化
swappiness 过高 → 内存压力时 Pod 被换出导致延迟飙升
文件描述符不足 → 大连接数场景下 accept 失败
本节提供经过生产验证的 sysctl 基线模板,按网络、内存、文件系统三个维度组织,每个参数均附带注释说明其作用与推荐依据。
部署原则:
所有配置通过
/etc/sysctl.d/99-k8s-*.conf文件管理,禁止使用sysctl -w临时修改配置变更必须通过 GitOps 流水线(Ansible/ArgoCD)下发,禁止手动 SSH 修改
每次变更后执行验证脚本(9.2.4),确认生效且无副作用
9.2.1 网络参数(补齐缺失的开头部分)
Bash
# /etc/sysctl.d/99-k8s-network.conf
# K8s 节点网络内核参数基线
# 适用:Linux Kernel 5.15+,K8s 1.28+
# 最后更新:2025-06
# === Conntrack(连接追踪)===
# K8s 每个 Service 连接都会被 conntrack 追踪
# 默认值 262144 在 >100 Pod 的节点上极易溢出
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_buckets = 262144
# 缩短 TCP 超时,加速 conntrack 表项回收
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
# UDP 超时(DNS 相关,默认 30s 已合理)
net.netfilter.nf_conntrack_udp_timeout = 30
net.netfilter.nf_conntrack_udp_timeout_stream = 120
# === TCP 优化 ===
net.ipv4.tcp_tw_reuse = 1 # 允许重用 TIME_WAIT 连接(出站)
net.ipv4.tcp_fin_timeout = 15 # 缩短 FIN_WAIT2 超时(默认60s)
net.ipv4.tcp_max_tw_buckets = 65536 # TIME_WAIT 上限(超出直接 RST)
net.ipv4.tcp_syncookies = 1 # 防 SYN Flood 攻击
net.ipv4.tcp_max_syn_backlog = 65535 # SYN 半连接队列大小
net.core.somaxconn = 65535 # listen 全连接队列大小
net.ipv4.tcp_rmem = 4096 87380 16777216 # TCP 接收缓冲区(min/default/max)
net.ipv4.tcp_wmem = 4096 65536 16777216 # TCP 发送缓冲区(min/default/max)
net.ipv4.tcp_mtu_probing = 1 # 启用 MTU 探测(避免黑洞路由)
net.ipv4.tcp_slow_start_after_idle = 0 # 禁用空闲后慢启动(长连接场景)
net.ipv4.tcp_no_metrics_save = 1 # 不缓存 TCP 连接指标
# === 网络核心 ===
net.core.netdev_max_backlog = 65536 # 网卡接收队列长度(默认1000)
net.core.rmem_max = 16777216 # 接收缓冲区上限
net.core.wmem_max = 16777216 # 发送缓冲区上限
net.core.default_qdisc = fq # 公平队列调度器(配合 tcp_bbr)
net.core.optmem_max = 65536 # 辅助缓冲区上限
net.ipv4.ip_local_port_range = 1024 65535 # 本地端口范围(扩大,默认32768-60999)
# === 桥接(K8s 必需)===
# 确保桥接流量经过 iptables/nftables 处理
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
# === ARP 优化(大集群)===
# 默认 gc_thresh 过小,大集群中 ARP 表频繁回收导致网络抖动
net.ipv4.neigh.default.gc_thresh1 = 4096
net.ipv4.neigh.default.gc_thresh2 = 8192
net.ipv4.neigh.default.gc_thresh3 = 16384
net.ipv4.neigh.default.gc_stale_time = 120
# === IPv6(如不使用则禁用,减少攻击面)===
# net.ipv6.conf.all.disable_ipv6 = 1
# net.ipv6.conf.default.disable_ipv6 = 1参数选择依据:
9.3.2 关键参数说明(补齐缺失表格)
9.4.1 选型对比(补齐缺失表格)
选型决策树:
Plain
是否需要 L7 NetworkPolicy(HTTP/gRPC 级别)?
├─ 是 → Cilium(唯一原生支持)
└─ 否 → 集群规模?
├─ < 100 节点 → Flannel(简单够用)或 Calico BGP
├─ 100-1000 节点 → Calico BGP/eBPF
└─ > 1000 节点 → Cilium 或 Calico eBPF
└─ 是否需要替代 kube-proxy?
├─ 是 → Cilium(kube-proxy-replacement: strict)
└─ 否 → Calico eBPF + IPVS性能压测基准(选型前必做):
Bash
# 使用 netperf 测试 CNI 网络性能
# 1. 吞吐量(TCP_STREAM)
netperf -H <pod-ip> -t TCP_STREAM -l 30 -- -m 1400
# 2. 延迟(TCP_RR)
netperf -H <pod-ip> -t TCP_RR -l 30 -- -r 1,1
# 3. PPS(UDP_STREAM,小包)
netperf -H <pod-ip> -t UDP_STREAM -l 30 -- -m 64
# 4. Service 规则规模测试
# 创建 5000 个 Service,观察 iptables/ipvs/eBPF 规则同步延迟
for i in $(seq 1 5000); do
kubectl create service clusterip svc-$i --tcp=80:80 &
done
# 5. 对比指标
# - 规则同步延迟(从 Service 创建到 Pod 可访问)
# - 首包延迟(新连接建立时间)
# - CPU 开销(kube-proxy / cilium-agent 的 CPU 使用率)9.9 实战 Lab(新增)
Lab 9.1:sysctl 基线部署与验证
Bash
# 步骤1:部署基线配置文件
cat > /etc/sysctl.d/99-k8s-network.conf << 'EOF'
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_buckets = 262144
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.core.netdev_max_backlog = 65536
net.core.somaxconn = 65535
net.ipv4.ip_local_port_range = 1024 65535
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.ipv4.neigh.default.gc_thresh1 = 4096
net.ipv4.neigh.default.gc_thresh2 = 8192
net.ipv4.neigh.default.gc_thresh3 = 16384
EOF
cat > /etc/sysctl.d/99-k8s-memory.conf << 'EOF'
vm.swappiness = 1
vm.min_free_kbytes = 1048576
vm.overcommit_memory = 1
vm.dirty_ratio = 40
vm.dirty_background_ratio = 10
vm.vfs_cache_pressure = 200
vm.zone_reclaim_mode = 0
EOF
cat > /etc/sysctl.d/99-k8s-fs.conf << 'EOF'
fs.inotify.max_user_watches = 1048576
fs.inotify.max_user_instances = 8192
fs.file-max = 2097152
fs.nr_open = 2097152
kernel.pid_max = 4194304
kernel.panic = 10
kernel.panic_on_oops = 1
kernel.hung_task_timeout_secs = 120
EOF
# 步骤2:应用配置
sysctl --system
# 步骤3:运行验证脚本
bash verify-sysctl-baseline.sh
# 预期输出:All sysctl checks passed
# 步骤4:验证 K8s 功能不受影响
kubectl get nodes
kubectl run test-net --image=busybox --restart=Never -- wget -qO- http://kubernetes.default.svc/healthz
kubectl delete pod test-netLab 9.2:Kubelet 配置灰度变更
Bash
# 步骤1:选择 1 个测试节点
TEST_NODE="node-worker-01"
# 步骤2:备份当前配置
ssh $TEST_NODE "cp /var/lib/kubelet/config.yaml /var/lib/kubelet/config.yaml.bak.$(date +%Y%m%d)"
# 步骤3:修改配置(示例:调整日志轮转)
ssh $TEST_NODE "sed -i 's/containerLogMaxSize:.*/containerLogMaxSize: \"100Mi\"/' /var/lib/kubelet/config.yaml"
ssh $TEST_NODE "sed -i 's/containerLogMaxFiles:.*/containerLogMaxFiles: 5/' /var/lib/kubelet/config.yaml"
# 步骤4:重启 kubelet
ssh $TEST_NODE "systemctl restart kubelet"
# 步骤5:验证节点状态
kubectl get node $TEST_NODE
# 应为 Ready
# 步骤6:验证 Pod 正常运行
kubectl get pods --field-selector spec.nodeName=$TEST_NODE
# 所有 Pod 应为 Running
# 步骤7:观察 30 分钟,确认无异常后推广到 10% 节点
# 步骤8:全量推广Lab 9.3:CNI 性能对比测试
Bash
# 步骤1:部署 netperf 测试 Pod
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: netperf-server
labels:
app: netperf
spec:
containers:
- name: netperf
image: networkstatic/netperf
command: ["netserver", "-D"]
ports:
- containerPort: 12865
---
apiVersion: v1
kind: Pod
metadata:
name: netperf-client
spec:
containers:
- name: netperf
image: networkstatic/netperf
command: ["sleep", "3600"]
EOF
# 步骤2:等待 Pod Ready
kubectl wait --for=condition=Ready pod/netperf-server --timeout=60s
kubectl wait --for=condition=Ready pod/netperf-client --timeout=60s
# 步骤3:获取 Server Pod IP
SERVER_IP=$(kubectl get pod netperf-server -o jsonpath='{.status.podIP}')
# 步骤4:TCP 吞吐量测试
kubectl exec netperf-client -- netperf -H $SERVER_IP -t TCP_STREAM -l 30
# 记录 Throughput(Mbps)
# 步骤5:TCP 延迟测试
kubectl exec netperf-client -- netperf -H $SERVER_IP -t TCP_RR -l 30
# 记录 Transaction Rate(trans/s)
# 步骤6:UDP PPS 测试
kubectl exec netperf-client -- netperf -H $SERVER_IP -t UDP_STREAM -l 30 -- -m 64
# 记录 Throughput(10^6bits/s)
# 步骤7:跨节点测试(将 client 调度到不同节点)
# 对比同节点 vs 跨节点性能差异
# 步骤8:清理
kubectl delete pod netperf-server netperf-client9.10 常见误区(新增)
9.11 本章小结(新增)
本章将前八章中零散出现的调优参数系统化为可审计、可落地、可追溯的生产基线:
Plain
┌─────────────────────────────────────────────────────────┐
│ 第9章知识体系 │
├─────────────────────────────────────────────────────────┤
│ │
│ 内核层(sysctl) │
│ ├─ 网络:conntrack / TCP / 桥接 / ARP │
│ ├─ 内存:swappiness / dirty / NUMA / OOM │
│ └─ 文件系统:inotify / fd / pid / panic │
│ │
│ K8s 层(Kubelet) │
│ ├─ 资源预留与驱逐 │
│ ├─ 镜像管理与日志轮转 │
│ ├─ CPU/内存/拓扑管理器 │
│ └─ 健康检查与认证授权 │
│ │
│ 网络层(CNI + kube-proxy) │
│ ├─ 选型:Flannel → Calico → Cilium │
│ ├─ 演进:iptables → IPVS → eBPF │
│ └─ 调优:conntrack 表 / 规则同步 / Hubble │
│ │
│ 运行时层(containerd) │
│ ├─ SystemdCgroup 一致性 │
│ ├─ 镜像拉取并发控制 │
│ └─ shim 进程管理 │
│ │
│ 治理层(审计与变更) │
│ ├─ 自动化审计脚本 │
│ ├─ GitOps 配置管理(Ansible + ArgoCD) │
│ └─ 灰度发布与回滚 │
│ │
└─────────────────────────────────────────────────────────┘核心原则:
配置即代码:所有基线通过 Git 仓库管理,变更通过 PR 审批
持续合规:每季度自动审计,偏差自动告警
灰度变更:任何配置变更遵循 1 节点 → 10% → 全量的发布节奏
可回滚:每次变更前自动备份,保留最近 5 个版本
以上为第9章全部缺失内容的补齐。将上述内容插入原文档对应位置后,第9章结构完整,包含:学习目标 → 内核基线(网络/内存/文件系统)→ Kubelet 配置 → CNI 选型 → 运行时调优 → 基线审计 → Checklist → 思考题 → 实战 Lab → 常见误区 → 本章小结。
第十章:综合实战——全链路故障复盘工作坊
10.1 学习目标
完成本章学习后,你将能够:
独立完成从告警→止血→取证→根因→修复→预防的全流程
撰写符合 SRE 标准的 RCA(Root Cause Analysis)报告
将个案转化为 Checklist / 告警规则 / 自愈脚本 / 基线变更
组织高效的故障复盘会议(Blameless Postmortem)
10.2 Case Study A:NVMe 驱动 Bug 导致节点假死
10.2.1 故障现象
Plain
时间:2025-06-09 03:22 UTC
告警:
[P1] NodeCPULoadHigh - node-worker-07 load5=210 (96核)
[P1] NodeDiskIOSaturation - node-worker-07 %util=100%
[P0] NodeNotReady - node-worker-07 (5分钟后)
[P0] PodEviction - 47 Pods evicted from node-worker-07
业务影响:
- 3 个核心微服务 P99 延迟从 5ms 飙升到 2000ms
- 订单服务错误率从 0.01% 升至 12%
- 持续 23 分钟10.2.2 排查时间线
Plain
03:22:15 Alertmanager 触发 NodeCPULoadHigh
03:22:30 值班 SRE 收到 P1 告警,SSH 登录节点
03:23:00 SSH 响应极慢(>30s),top 显示 Load=210,CPU Usage=35%
03:23:15 执行 ps -eo stat | grep -c D → 输出:89(89个D-State进程!)
03:23:30 iostat -xz 1 → nvme0n1 %util=100%, await=850ms
03:24:00 dmesg -T | grep -i nvme → "nvme nvme0: I/O 45678 timeout"
03:24:30 判断:NVMe 驱动 I/O 超时导致 D-State 堆积
03:25:00 止血:kubectl cordon node-worker-07
03:25:30 kubectl drain node-worker-07 --timeout=60s --force
03:26:00 网关限流:订单服务 RPS 从 5000 降至 2000
03:28:00 业务 P99 开始回落
03:35:00 业务完全恢复
03:40:00 节点仍无响应,通过 IPMI SOL 获取 Serial Console 日志
03:42:00 确认内核日志:"nvme nvme0: controller is down; will reset"
04:00:00 云厂商确认:NVMe 固件 Bug(特定批次)10.2.3 根因分析(5 Whys)
Plain
Why 1: 为什么节点假死?
→ 89 个进程处于 D-State,等待 NVMe I/O 完成
Why 2: 为什么 NVMe I/O 超时?
→ NVMe 控制器固件 Bug,在高 IOPS 下触发内部死锁
Why 3: 为什么高 IOPS?
→ 节点上运行了 12 个 Java 应用,GC 日志 + 应用日志写入峰值达 80K IOPS
Why 4: 为什么日志写入如此集中?
→ 未配置日志轮转,且 3 个应用使用了同步日志框架(Log4j SyncAppender)
Why 5: 为什么没有提前发现?
→ 缺少 NVMe 健康监控(SMART 数据)和 IOPS 趋势告警10.2.4 修复与预防
10.3 Case Study B:CFS Throttling 导致 Java Pod 循环重启
10.3.1 故障现象
Plain
时间:2025-07-15 14:30 UTC
告警:
[P1] PodCrashLooping - payment-service-7d8f9 (RestartCount=15)
[P2] ContainerCPUThrottling - payment-service throttled_ratio=35%
业务影响:
- 支付服务可用性从 99.99% 降至 97.5%
- 每次重启导致 30s 不可用窗口
- 持续 2 小时(直到人工介入)10.3.2 排查过程
Bash
# 步骤1:查看 Pod 事件
kubectl describe pod payment-service-7d8f9
# Events:
# Warning Unhealthy 2m kubelet Liveness probe failed: HTTP probe failed: context deadline exceeded
# Normal Killing 2m kubelet Container failed liveness probe, will be restarted
# 步骤2:查看 CFS 限流统计
POD_UID=$(kubectl get pod payment-service-7d8f9 -o jsonpath='{.metadata.uid}')
cat /sys/fs/cgroup/kubepods.slice/kubepods-burstable/kubepods-burstable-pod${POD_UID}.slice/cpu.stat
# nr_periods: 45230
# nr_throttled: 15830 ← 35% 的周期被限流!
# throttled_usec: 89200000 ← 累计被限流 89.2 秒
# 步骤3:查看 GC 日志
kubectl logs payment-service-7d8f9 --previous | grep "pause"
# [GC pause (G1 Evacuation Pause) (young), 0.8234567 secs] ← 正常应 < 0.1s
# [GC pause (G1 Humongous Allocation), 1.2345678 secs] ← 严重超时!
# 步骤4:Off-CPU 火焰图验证
PID=$(crictl inspect $(crictl ps --name payment -q) | jq '.info.pid')
/usr/share/bcc/tools/offcputime -df -p $PID 10 > offcpu.stacks
# 火焰图显示:大量时间在 schedule() → 被 CFS 挂起
# 步骤5:确认资源配置
kubectl get pod payment-service-7d8f9 -o jsonpath='{.spec.containers[0].resources}'
# {"limits":{"cpu":"1","memory":"2Gi"},"requests":{"cpu":"500m","memory":"1Gi"}}
# CPU Limit=1,但 Java 应用 + GC 线程需要突发到 2+ 核10.3.3 根因
Java 应用 CPU Limit=1 核,但 G1 GC 在突发流量下需要额外 CPU 时间。CFS 在配额用完后挂起所有线程(包括 GC 线程),导致 GC STW 时间从 50ms 延长到 800ms+,超过 Liveness Probe 超时(1s),Pod 被 Kill 重启,形成循环。
10.3.4 修复
YAML
# 修复后的资源配置
resources:
requests:
cpu: "1"
memory: "3Gi" # Heap(2G) × 1.5
limits:
cpu: "4" # 允许 GC 突发(4倍 Requests)
memory: "3Gi"
---
# JVM 参数调整
env:
- name: JAVA_OPTS
value: >-
-Xms2g -Xmx2g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:ActiveProcessorCount=4
-XX:+UseContainerSupport
-XX:+ParallelRefProcEnabled
---
# Liveness Probe 调整
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60 # 从 30s 增加到 60s
periodSeconds: 15 # 从 10s 增加到 15s
timeoutSeconds: 5 # 从 1s 增加到 5s
failureThreshold: 5 # 从 3 增加到 510.4 Case Study C:CoreDNS UDP conntrack 冲突导致微服务超时
10.4.1 故障现象
Plain
时间:2025-08-20 09:15 UTC
告警:
[P2] ServiceLatencyHigh - 多个微服务 P99 > 5s(偶发)
[P2] DNSResolutionSlow - gethostlatency avg=250ms
业务影响:
- 所有微服务偶发 5s 超时(约 2% 请求)
- 无规律,难以复现
- 持续 3 天(被误判为"网络抖动")10.4.2 排查过程
Bash
# 步骤1:eBPF 确认 DNS 延迟
/usr/share/bcc/tools/gethostlatency
# TIME PID COMM LATms HOST
# 09:15:23 12345 java 0.03 service-a.ns.svc.cluster.local
# 09:15:24 12345 java 253.10 service-b.ns.svc.cluster.local ← 偶发高延迟
# 09:15:25 12678 python 0.02 service-a.ns.svc.cluster.local
# 步骤2:排除 CoreDNS 资源问题
kubectl top pods -n kube-system -l k8s-app=kube-dns
# CPU/Memory 均正常(< 50%)
# 步骤3:检查 conntrack UDP 冲突(关键!)
conntrack -S
# cpu=0 found=0 invalid=0 insert=45230 insert_failed=8912 ← 关键!
# drop=0 early_drop=0
# insert_failed=8912 → UDP conntrack 插入冲突!
# 步骤4:理解根因
# Linux conntrack 对 UDP 的处理:
# 当两个并发的 DNS 查询(相同 src_ip:src_port → 53)到达时,
# 第二个包的 conntrack 插入会失败(insert_failed++),
# 导致该 DNS 响应包被丢弃,应用等待超时后重试
# 步骤5:验证
# 在高并发节点上抓包
tcpdump -i eth0 port 53 -nn -c 100
# 观察是否有 DNS 查询发出但无响应10.4.3 根因
Linux 内核 conntrack 模块在处理并发 UDP 连接时存在已知的竞态条件(race condition)。当多个 Pod 同时发起 DNS 查询,且源端口恰好相同时,第二个 conntrack 条目插入失败,DNS 响应包被丢弃。应用等待 5 秒超时后重试才成功。
10.4.4 修复
YAML
# 方案一:部署 NodeLocal DNSCache(推荐)
# 每个节点运行一个本地 DNS 缓存,使用 TCP 连接 CoreDNS(避免 UDP 冲突)
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-local-dns
namespace: kube-system
spec:
template:
spec:
containers:
- name: node-cache
image: registry.k8s.io/dns/k8s-dns-node-cache:1.22.28
args:
- "-localip"
- "169.254.20.10"
- "-conf"
- "/etc/Corefile"
- "-upstreamsvc"
- "kube-dns"
securityContext:
privileged: true
ports:
- containerPort: 53
name: dns
protocol: UDP
- containerPort: 53
name: dns-tcp
protocol: TCP
---
# 方案二:Pod DNS 配置优化
# 减少不必要的 DNS 查询
spec:
dnsConfig:
options:
- name: ndots
value: "2" # 从默认 5 降为 2(减少搜索域查询次数)
- name: single-request-reopen
# 强制 A 和 AAAA 查询使用不同 socket(避免 conntrack 冲突)
- name: timeout
value: "1" # 超时从 5s 降为 1s(快速重试)
- name: attempts
value: "3"10.5 RCA 报告标准模板
Markdown
# RCA 报告:[故障标题]
## 元信息
| 项目 | 内容 |
|------|------|
| 报告编号 | RCA-2025-0609-001 |
| 严重等级 | P0 / P1 / P2 |
| 影响时长 | XX 分钟 |
| 影响范围 | XX 服务 / XX% 用户 |
| 值班人员 | @name |
| 报告撰写 | @name |
| 复盘日期 | YYYY-MM-DD |
## 1. 故障摘要(Executive Summary)
> 一段话概括:什么时间、什么系统、发生了什么、影响了什么、如何恢复的。
## 2. 影响评估
- 业务影响:[量化 RED 指标变化]
- 用户影响:[受影响用户数/比例]
- 财务影响:[如有]
- SLA 影响:[是否违反 SLA]
## 3. 时间线(精确到秒)
| 时间(UTC) | 事件 | 操作人 |
|-----------|------|--------|
| 03:22:15 | Alertmanager 触发 NodeCPULoadHigh | 系统 |
| 03:22:30 | SRE 收到告警,开始排查 | @name |
| ... | ... | ... |
## 4. 止血动作与有效性验证
- 止血动作:[具体命令/操作]
- 验证指标:
- [ ] 热点核心利用率 ≤ 75%
- [ ] 业务 RT 回落到基线
- [ ] 错误率恢复正常
## 5. 排查路径
### 5.1 初始假设与排除
| 假设 | 验证方法 | 结果 | 排除/确认 |
|------|----------|------|-----------|
| CPU 不足 | mpstat | Usage=35% | 排除 |
| 网络故障 | ping/traceroute | 正常 | 排除 |
| 存储故障 | iostat | %util=100% | 确认 |
### 5.2 根因定位过程
[详细排查步骤、使用的命令、观察到的数据]
## 6. 根因(5 Whys)
[逐层追问,直到根本原因]
## 7. 修复措施
| 类别 | 措施 | 负责人 | 截止日期 | 状态 |
|------|------|--------|----------|------|
| 临时 | ... | ... | ... | ✅ |
| 短期 | ... | ... | ... | 🔄 |
| 长期 | ... | ... | ... | ⏳ |
## 8. 预防项
- [ ] 新增告警规则:[PromQL]
- [ ] 新增 NPD 检测规则:[pattern]
- [ ] 更新基线配置:[参数]
- [ ] 更新 Runbook:[链接]
- [ ] 新增自愈脚本:[路径]
- [ ] 培训计划:[内容/日期]
## 9. 经验教训
### 做得好的
- ...
### 需要改进的
- ...
### 运气成分
- ...
## 10. 附件
- 监控截图
- 火焰图
- 内核日志
- 相关 PR/变更链接10.6 Blameless Postmortem 会议指南
10.6.1 会议原则
对事不对人:关注系统和流程缺陷,而非个人失误
假设善意:每个人都在当时信息下做了最合理的决策
鼓励透明:只有坦诚才能发现真正的系统缺陷
产出导向:每个 Action Item 必须有负责人和截止日期
10.6.2 会议议程(60 分钟)
10.6.3 从个案到体系
Plain
个案故障
↓
RCA 报告
↓
┌─────────────────────────────────────────┐
│ 转化为体系化预防措施 │
├─────────────────────────────────────────┤
│ → 新增/修改告警规则(Prometheus) │
│ → 新增 NPD 检测模式 │
│ → 更新节点基线配置(sysctl/kubelet) │
│ → 新增自愈脚本(Ansible Playbook) │
│ → 更新 Runbook(排查 SOP) │
│ → 新增 Lab 实验(培训教材) │
│ → 更新准入控制策略(OPA/Kyverno) │
│ → 纳入 Game Day 演练场景 │
└─────────────────────────────────────────┘10.7 结业考核
考核形式
在隔离的实验环境中,讲师注入一个复合故障(组合以下至少 2 种):
CFS Throttling + DNS 延迟
SoftIRQ 风暴 + conntrack 溢出
D-State I/O 阻塞 + 监控断连
NUMA 跨节点访问 + CPU Steal
考核标准
通过标准
总分 ≥ 80 分
止血速度 ≤ 5 分钟(一票否决项:> 10 分钟不通过)
根因定位正确(一票否决项:根因错误不通过)
附录
附录 A:Linux 性能工具速查表
附录 B:K8s 节点排查流程图
Plain
┌──────────────┐
│ 告警触发 │
└──────┬───────┘
│
┌──────────▼──────────┐
│ 业务是否受损? │
└──────────┬──────────┘
│ │
是 否
│ │
┌──────────▼───┐ │
│ 立即止血 │ │
│ cordon+drain │ │
│ + 网关限流 │ │
└──────────┬───┘ │
│ │
┌──────────▼───┐ │
│ 验证恢复 │ │
│ 3项指标 │ │
└──────────┬───┘ │
│ │
└─────┬─────┘
│
┌──────────▼──────────┐
│ 采集宏观指标 │
│ mpstat/iostat/ss/sar │
└──────────┬──────────┘
│
┌────────┬───────┬───┴────┬────────┬────────┐
│ │ │ │ │ │
%soft高 %wa高 %sys高 %steal高 Throttle 假死
│ │ │ │ │ │
IRQ/RPS I/O排查 conntrack 云厂商 cpu.stat Serial
小包洪峰 D-State iptables 独占实例 CPU Burst kdump
协议栈 CSI/NFS perf top Anti-Aff 调Limit NPD
│ │ │ │ │ │
└────────┴───────┴───┬────┴────────┴────────┘
│
┌──────────▼──────────┐
│ 根因确认 │
│ eBPF/Perf 微观验证 │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ 修复 + 预防 │
│ 基线更新/告警/自愈 │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ RCA 报告归档 │
│ 复盘会议 │
└─────────────────────┘附录 C:生产配置模板清单
附录 D:术语表
附录 E:参考资料
书籍
Brendan Gregg,《Systems Performance: Enterprise and the Cloud》(2nd Edition), 2020
Brendan Gregg,《BPF Performance Tools》, 2019
Google SRE Team,《Site Reliability Engineering》, 2016
Google SRE Team,《The Site Reliability Workbook》, 2018
官方文档
Linux Kernel Documentation: https://www.kernel.org/doc/html/latest/
Kubernetes Documentation: https://kubernetes.io/docs/
Cilium Documentation: https://docs.cilium.io/
BCC Tools Reference: https://github.com/iovisor/bcc
白皮书与博客
CNCF Observability Whitepaper, 2024
Netflix Tech Blog: "Linux Performance Analysis in 60,000 Milliseconds"
Cloudflare Blog: "eBPF: The Future of Networking"
Red Hat Blog: "Understanding CPU Throttling in Kubernetes"
教材使用建议
版本:v2.0(2026-07-31) 基于:原始大纲深度重构,修复逻辑断裂、消除内容重复、补齐教材要素、强化预防体系与现代工具链 许可:内部培训使用,未经授权不得外传 维护:每季度根据内核/K8s 版本更新内容,每年进行一次全面修订
— 全书完 —